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
|
.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)")
|
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]")
|
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)")
|
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):
|
class RaceInfo(BaseModel):
|
||||||
@@ -32,6 +34,8 @@ class DriverState(BaseModel):
|
|||||||
current_tire_compound: Literal["soft", "medium", "hard", "intermediate", "wet"] = Field(..., description="Current tire compound")
|
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")
|
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")
|
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):
|
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)
|
latest = max(enriched_telemetry, key=lambda x: x.lap)
|
||||||
pit_window = latest.optimal_pit_window
|
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:
|
if count == 1:
|
||||||
# Ultra-fast mode: just generate 1 strategy
|
# 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}.
|
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"}}]}}"""
|
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
|
# 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}.
|
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"}}]}}"""
|
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}.
|
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"}}]}}"""
|
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,
|
"pace_trend": t.pace_trend,
|
||||||
"tire_cliff_risk": round(t.tire_cliff_risk, 3),
|
"tire_cliff_risk": round(t.tire_cliff_risk, 3),
|
||||||
"optimal_pit_window": t.optimal_pit_window,
|
"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
|
# Format competitors
|
||||||
@@ -92,12 +108,14 @@ def build_brainstorm_prompt(
|
|||||||
"gap_seconds": round(c.gap_seconds, 1)
|
"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:
|
LAP-LEVEL TELEMETRY METRICS:
|
||||||
- tire_degradation_rate: 0-1 (higher = worse tire wear)
|
- tire_degradation_rate: 0-1 (higher = worse tire wear)
|
||||||
- tire_cliff_risk: 0-1 (probability of hitting tire cliff)
|
- tire_cliff_risk: 0-1 (probability of hitting tire cliff)
|
||||||
- pace_trend: "improving", "stable", or "declining"
|
- 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
|
- optimal_pit_window: [start_lap, end_lap] recommended pit range
|
||||||
- performance_delta: seconds vs baseline (negative = slower)
|
- 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": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 1,
|
"execution_count": 4,
|
||||||
"id": "be0c6fbf",
|
"id": "be0c6fbf",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 5,
|
||||||
"id": "f757cb34",
|
"id": "f757cb34",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
"name": "stderr",
|
"name": "stderr",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"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",
|
"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 session_info\n",
|
||||||
"req INFO \tUsing cached data for driver_info\n",
|
"req INFO \tUsing cached data for driver_info\n",
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 6,
|
||||||
"id": "aa5e5d8f",
|
"id": "aa5e5d8f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -136,6 +135,9 @@
|
|||||||
" <th></th>\n",
|
" <th></th>\n",
|
||||||
" <th>lap_number</th>\n",
|
" <th>lap_number</th>\n",
|
||||||
" <th>total_laps</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>lap_time</th>\n",
|
||||||
" <th>average_speed</th>\n",
|
" <th>average_speed</th>\n",
|
||||||
" <th>max_speed</th>\n",
|
" <th>max_speed</th>\n",
|
||||||
@@ -150,6 +152,9 @@
|
|||||||
" <th>0</th>\n",
|
" <th>0</th>\n",
|
||||||
" <td>1</td>\n",
|
" <td>1</td>\n",
|
||||||
" <td>51</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>0 days 00:01:33.340000</td>\n",
|
||||||
" <td>210.17</td>\n",
|
" <td>210.17</td>\n",
|
||||||
" <td>326.0</td>\n",
|
" <td>326.0</td>\n",
|
||||||
@@ -162,6 +167,9 @@
|
|||||||
" <th>1</th>\n",
|
" <th>1</th>\n",
|
||||||
" <td>2</td>\n",
|
" <td>2</td>\n",
|
||||||
" <td>51</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>0 days 00:01:28.012000</td>\n",
|
||||||
" <td>236.87</td>\n",
|
" <td>236.87</td>\n",
|
||||||
" <td>330.0</td>\n",
|
" <td>330.0</td>\n",
|
||||||
@@ -174,6 +182,9 @@
|
|||||||
" <th>2</th>\n",
|
" <th>2</th>\n",
|
||||||
" <td>3</td>\n",
|
" <td>3</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.546000</td>\n",
|
||||||
" <td>236.40</td>\n",
|
" <td>236.40</td>\n",
|
||||||
" <td>331.0</td>\n",
|
" <td>331.0</td>\n",
|
||||||
@@ -186,6 +197,9 @@
|
|||||||
" <th>3</th>\n",
|
" <th>3</th>\n",
|
||||||
" <td>4</td>\n",
|
" <td>4</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.221000</td>\n",
|
||||||
" <td>240.13</td>\n",
|
" <td>240.13</td>\n",
|
||||||
" <td>341.0</td>\n",
|
" <td>341.0</td>\n",
|
||||||
@@ -198,6 +212,9 @@
|
|||||||
" <th>4</th>\n",
|
" <th>4</th>\n",
|
||||||
" <td>5</td>\n",
|
" <td>5</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.033000</td>\n",
|
||||||
" <td>236.09</td>\n",
|
" <td>236.09</td>\n",
|
||||||
" <td>345.0</td>\n",
|
" <td>345.0</td>\n",
|
||||||
@@ -210,6 +227,9 @@
|
|||||||
" <th>5</th>\n",
|
" <th>5</th>\n",
|
||||||
" <td>6</td>\n",
|
" <td>6</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.175000</td>\n",
|
||||||
" <td>236.74</td>\n",
|
" <td>236.74</td>\n",
|
||||||
" <td>343.0</td>\n",
|
" <td>343.0</td>\n",
|
||||||
@@ -222,6 +242,9 @@
|
|||||||
" <th>6</th>\n",
|
" <th>6</th>\n",
|
||||||
" <td>7</td>\n",
|
" <td>7</td>\n",
|
||||||
" <td>51</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>0 days 00:01:26.929000</td>\n",
|
||||||
" <td>239.72</td>\n",
|
" <td>239.72</td>\n",
|
||||||
" <td>340.0</td>\n",
|
" <td>340.0</td>\n",
|
||||||
@@ -234,6 +257,9 @@
|
|||||||
" <th>7</th>\n",
|
" <th>7</th>\n",
|
||||||
" <td>8</td>\n",
|
" <td>8</td>\n",
|
||||||
" <td>51</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>0 days 00:01:26.943000</td>\n",
|
||||||
" <td>238.45</td>\n",
|
" <td>238.45</td>\n",
|
||||||
" <td>351.0</td>\n",
|
" <td>351.0</td>\n",
|
||||||
@@ -246,6 +272,9 @@
|
|||||||
" <th>8</th>\n",
|
" <th>8</th>\n",
|
||||||
" <td>9</td>\n",
|
" <td>9</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.383000</td>\n",
|
||||||
" <td>236.81</td>\n",
|
" <td>236.81</td>\n",
|
||||||
" <td>330.0</td>\n",
|
" <td>330.0</td>\n",
|
||||||
@@ -258,6 +287,9 @@
|
|||||||
" <th>9</th>\n",
|
" <th>9</th>\n",
|
||||||
" <td>10</td>\n",
|
" <td>10</td>\n",
|
||||||
" <td>51</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>0 days 00:01:27.368000</td>\n",
|
||||||
" <td>232.42</td>\n",
|
" <td>232.42</td>\n",
|
||||||
" <td>331.0</td>\n",
|
" <td>331.0</td>\n",
|
||||||
@@ -271,32 +303,44 @@
|
|||||||
"</div>"
|
"</div>"
|
||||||
],
|
],
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
" lap_number total_laps lap_time average_speed max_speed \\\n",
|
" lap_number total_laps position gap_to_leader gap_to_ahead \\\n",
|
||||||
"0 1 51 0 days 00:01:33.340000 210.17 326.0 \n",
|
"0 1 51 11 6.223 0.620 \n",
|
||||||
"1 2 51 0 days 00:01:28.012000 236.87 330.0 \n",
|
"1 2 51 11 8.229 0.712 \n",
|
||||||
"2 3 51 0 days 00:01:27.546000 236.40 331.0 \n",
|
"2 3 51 11 9.799 0.898 \n",
|
||||||
"3 4 51 0 days 00:01:27.221000 240.13 341.0 \n",
|
"3 4 51 11 10.953 0.919 \n",
|
||||||
"4 5 51 0 days 00:01:27.033000 236.09 345.0 \n",
|
"4 5 51 11 11.563 0.917 \n",
|
||||||
"5 6 51 0 days 00:01:27.175000 236.74 343.0 \n",
|
"5 6 51 11 12.123 0.923 \n",
|
||||||
"6 7 51 0 days 00:01:26.929000 239.72 340.0 \n",
|
"6 7 51 11 12.694 0.387 \n",
|
||||||
"7 8 51 0 days 00:01:26.943000 238.45 351.0 \n",
|
"7 8 51 10 13.413 2.760 \n",
|
||||||
"8 9 51 0 days 00:01:27.383000 236.81 330.0 \n",
|
"8 9 51 10 14.320 3.387 \n",
|
||||||
"9 10 51 0 days 00:01:27.368000 232.42 331.0 \n",
|
"9 10 51 10 15.177 3.760 \n",
|
||||||
"\n",
|
"\n",
|
||||||
" tire_compound tire_life_laps track_temperature rainfall \n",
|
" lap_time average_speed max_speed tire_compound \\\n",
|
||||||
"0 MEDIUM 1 42.5 False \n",
|
"0 0 days 00:01:33.340000 210.17 326.0 MEDIUM \n",
|
||||||
"1 MEDIUM 2 42.5 False \n",
|
"1 0 days 00:01:28.012000 236.87 330.0 MEDIUM \n",
|
||||||
"2 MEDIUM 3 43.2 False \n",
|
"2 0 days 00:01:27.546000 236.40 331.0 MEDIUM \n",
|
||||||
"3 MEDIUM 4 43.2 False \n",
|
"3 0 days 00:01:27.221000 240.13 341.0 MEDIUM \n",
|
||||||
"4 MEDIUM 5 43.1 False \n",
|
"4 0 days 00:01:27.033000 236.09 345.0 MEDIUM \n",
|
||||||
"5 MEDIUM 6 43.3 False \n",
|
"5 0 days 00:01:27.175000 236.74 343.0 MEDIUM \n",
|
||||||
"6 MEDIUM 7 43.6 False \n",
|
"6 0 days 00:01:26.929000 239.72 340.0 MEDIUM \n",
|
||||||
"7 MEDIUM 8 43.6 False \n",
|
"7 0 days 00:01:26.943000 238.45 351.0 MEDIUM \n",
|
||||||
"8 MEDIUM 9 43.6 False \n",
|
"8 0 days 00:01:27.383000 236.81 330.0 MEDIUM \n",
|
||||||
"9 MEDIUM 10 43.9 False "
|
"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": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@@ -306,7 +350,10 @@
|
|||||||
"weather = session.weather_data\n",
|
"weather = session.weather_data\n",
|
||||||
"weather['SessionTime'] = pd.to_timedelta(weather['Time'])\n",
|
"weather['SessionTime'] = pd.to_timedelta(weather['Time'])\n",
|
||||||
"\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",
|
"lap_data_list = []\n",
|
||||||
"\n",
|
"\n",
|
||||||
"for lap_idx in driver_laps.index:\n",
|
"for lap_idx in driver_laps.index:\n",
|
||||||
@@ -332,6 +379,43 @@
|
|||||||
" tire_compound = lap['Compound']\n",
|
" tire_compound = lap['Compound']\n",
|
||||||
" tire_life = lap['TyreLife']\n",
|
" tire_life = lap['TyreLife']\n",
|
||||||
" \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",
|
" # Get weather data for this lap (use lap start time)\n",
|
||||||
" lap_start_time = pd.to_timedelta(lap['LapStartTime'])\n",
|
" lap_start_time = pd.to_timedelta(lap['LapStartTime'])\n",
|
||||||
" \n",
|
" \n",
|
||||||
@@ -344,6 +428,9 @@
|
|||||||
" lap_record = {\n",
|
" lap_record = {\n",
|
||||||
" 'lap_number': int(lap_number),\n",
|
" 'lap_number': int(lap_number),\n",
|
||||||
" 'total_laps': int(total_laps),\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",
|
" 'lap_time': lap_time,\n",
|
||||||
" 'average_speed': round(avg_speed, 2),\n",
|
" 'average_speed': round(avg_speed, 2),\n",
|
||||||
" 'max_speed': round(max_speed, 2),\n",
|
" 'max_speed': round(max_speed, 2),\n",
|
||||||
@@ -359,7 +446,7 @@
|
|||||||
" if lap_number % 10 == 0:\n",
|
" if lap_number % 10 == 0:\n",
|
||||||
" print(f\"Processed lap {int(lap_number)}...\")\n",
|
" print(f\"Processed lap {int(lap_number)}...\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# 5. Create final dataframe\n",
|
"# 6. Create final dataframe\n",
|
||||||
"laps_df = pd.DataFrame(lap_data_list)\n",
|
"laps_df = pd.DataFrame(lap_data_list)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(f\"\\n✓ Created lap-level dataframe with {len(laps_df)} laps\")\n",
|
"print(f\"\\n✓ Created lap-level dataframe with {len(laps_df)} laps\")\n",
|
||||||
@@ -369,7 +456,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 7,
|
||||||
"id": "b1086b8d",
|
"id": "b1086b8d",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -383,6 +470,9 @@
|
|||||||
"Column types:\n",
|
"Column types:\n",
|
||||||
"lap_number int64\n",
|
"lap_number int64\n",
|
||||||
"total_laps int64\n",
|
"total_laps int64\n",
|
||||||
|
"position int64\n",
|
||||||
|
"gap_to_leader float64\n",
|
||||||
|
"gap_to_ahead float64\n",
|
||||||
"lap_time timedelta64[ns]\n",
|
"lap_time timedelta64[ns]\n",
|
||||||
"average_speed float64\n",
|
"average_speed float64\n",
|
||||||
"max_speed float64\n",
|
"max_speed float64\n",
|
||||||
@@ -418,6 +508,9 @@
|
|||||||
" <th></th>\n",
|
" <th></th>\n",
|
||||||
" <th>lap_number</th>\n",
|
" <th>lap_number</th>\n",
|
||||||
" <th>total_laps</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>lap_time</th>\n",
|
||||||
" <th>average_speed</th>\n",
|
" <th>average_speed</th>\n",
|
||||||
" <th>max_speed</th>\n",
|
" <th>max_speed</th>\n",
|
||||||
@@ -430,6 +523,9 @@
|
|||||||
" <th>count</th>\n",
|
" <th>count</th>\n",
|
||||||
" <td>51.000000</td>\n",
|
" <td>51.000000</td>\n",
|
||||||
" <td>51.0</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</td>\n",
|
||||||
" <td>51.000000</td>\n",
|
" <td>51.000000</td>\n",
|
||||||
" <td>51.000000</td>\n",
|
" <td>51.000000</td>\n",
|
||||||
@@ -440,6 +536,9 @@
|
|||||||
" <th>mean</th>\n",
|
" <th>mean</th>\n",
|
||||||
" <td>26.000000</td>\n",
|
" <td>26.000000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:27.596803921</td>\n",
|
||||||
" <td>235.797059</td>\n",
|
" <td>235.797059</td>\n",
|
||||||
" <td>333.686275</td>\n",
|
" <td>333.686275</td>\n",
|
||||||
@@ -450,6 +549,9 @@
|
|||||||
" <th>std</th>\n",
|
" <th>std</th>\n",
|
||||||
" <td>14.866069</td>\n",
|
" <td>14.866069</td>\n",
|
||||||
" <td>0.0</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>0 days 00:00:03.069690434</td>\n",
|
||||||
" <td>7.855085</td>\n",
|
" <td>7.855085</td>\n",
|
||||||
" <td>4.921342</td>\n",
|
" <td>4.921342</td>\n",
|
||||||
@@ -460,6 +562,9 @@
|
|||||||
" <th>min</th>\n",
|
" <th>min</th>\n",
|
||||||
" <td>1.000000</td>\n",
|
" <td>1.000000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:26.105000</td>\n",
|
||||||
" <td>191.140000</td>\n",
|
" <td>191.140000</td>\n",
|
||||||
" <td>322.000000</td>\n",
|
" <td>322.000000</td>\n",
|
||||||
@@ -470,6 +575,9 @@
|
|||||||
" <th>25%</th>\n",
|
" <th>25%</th>\n",
|
||||||
" <td>13.500000</td>\n",
|
" <td>13.500000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:26.715000</td>\n",
|
||||||
" <td>236.105000</td>\n",
|
" <td>236.105000</td>\n",
|
||||||
" <td>331.000000</td>\n",
|
" <td>331.000000</td>\n",
|
||||||
@@ -480,6 +588,9 @@
|
|||||||
" <th>50%</th>\n",
|
" <th>50%</th>\n",
|
||||||
" <td>26.000000</td>\n",
|
" <td>26.000000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:26.943000</td>\n",
|
||||||
" <td>237.130000</td>\n",
|
" <td>237.130000</td>\n",
|
||||||
" <td>332.000000</td>\n",
|
" <td>332.000000</td>\n",
|
||||||
@@ -490,6 +601,9 @@
|
|||||||
" <th>75%</th>\n",
|
" <th>75%</th>\n",
|
||||||
" <td>38.500000</td>\n",
|
" <td>38.500000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:27.328500</td>\n",
|
||||||
" <td>238.655000</td>\n",
|
" <td>238.655000</td>\n",
|
||||||
" <td>334.000000</td>\n",
|
" <td>334.000000</td>\n",
|
||||||
@@ -500,6 +614,9 @@
|
|||||||
" <th>max</th>\n",
|
" <th>max</th>\n",
|
||||||
" <td>51.000000</td>\n",
|
" <td>51.000000</td>\n",
|
||||||
" <td>51.0</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>0 days 00:01:47.272000</td>\n",
|
||||||
" <td>241.700000</td>\n",
|
" <td>241.700000</td>\n",
|
||||||
" <td>351.000000</td>\n",
|
" <td>351.000000</td>\n",
|
||||||
@@ -511,28 +628,38 @@
|
|||||||
"</div>"
|
"</div>"
|
||||||
],
|
],
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
" lap_number total_laps lap_time average_speed \\\n",
|
" lap_number total_laps position gap_to_leader gap_to_ahead \\\n",
|
||||||
"count 51.000000 51.0 51 51.000000 \n",
|
"count 51.000000 51.0 51.000000 51.000000 51.000000 \n",
|
||||||
"mean 26.000000 51.0 0 days 00:01:27.596803921 235.797059 \n",
|
"mean 26.000000 51.0 9.803922 29.418314 2.889235 \n",
|
||||||
"std 14.866069 0.0 0 days 00:00:03.069690434 7.855085 \n",
|
"std 14.866069 0.0 1.058671 13.842909 1.707825 \n",
|
||||||
"min 1.000000 51.0 0 days 00:01:26.105000 191.140000 \n",
|
"min 1.000000 51.0 6.000000 6.223000 0.387000 \n",
|
||||||
"25% 13.500000 51.0 0 days 00:01:26.715000 236.105000 \n",
|
"25% 13.500000 51.0 9.000000 16.928500 1.123000 \n",
|
||||||
"50% 26.000000 51.0 0 days 00:01:26.943000 237.130000 \n",
|
"50% 26.000000 51.0 10.000000 29.113000 2.799000 \n",
|
||||||
"75% 38.500000 51.0 0 days 00:01:27.328500 238.655000 \n",
|
"75% 38.500000 51.0 10.000000 43.405500 4.136500 \n",
|
||||||
"max 51.000000 51.0 0 days 00:01:47.272000 241.700000 \n",
|
"max 51.000000 51.0 12.000000 48.171000 8.135000 \n",
|
||||||
"\n",
|
"\n",
|
||||||
" max_speed tire_life_laps track_temperature \n",
|
" lap_time average_speed max_speed tire_life_laps \\\n",
|
||||||
"count 51.000000 51.000000 51.000000 \n",
|
"count 51 51.000000 51.000000 51.000000 \n",
|
||||||
"mean 333.686275 15.411765 42.898039 \n",
|
"mean 0 days 00:01:27.596803921 235.797059 333.686275 15.411765 \n",
|
||||||
"std 4.921342 8.616673 0.876924 \n",
|
"std 0 days 00:00:03.069690434 7.855085 4.921342 8.616673 \n",
|
||||||
"min 322.000000 1.000000 40.800000 \n",
|
"min 0 days 00:01:26.105000 191.140000 322.000000 1.000000 \n",
|
||||||
"25% 331.000000 8.500000 42.500000 \n",
|
"25% 0 days 00:01:26.715000 236.105000 331.000000 8.500000 \n",
|
||||||
"50% 332.000000 15.000000 43.100000 \n",
|
"50% 0 days 00:01:26.943000 237.130000 332.000000 15.000000 \n",
|
||||||
"75% 334.000000 21.000000 43.600000 \n",
|
"75% 0 days 00:01:27.328500 238.655000 334.000000 21.000000 \n",
|
||||||
"max 351.000000 33.000000 44.300000 "
|
"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": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@@ -549,7 +676,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 5,
|
"execution_count": 8,
|
||||||
"id": "b2a3b878",
|
"id": "b2a3b878",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -569,7 +696,55 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"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",
|
"id": "0202372e",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"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:
|
Accepted aliases for lap-level data:
|
||||||
- lap_number: lap, Lap, LapNumber, lap_number
|
- lap_number: lap, Lap, LapNumber, lap_number
|
||||||
- total_laps: TotalLaps, total_laps
|
- 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
|
- lap_time: lap_time, LapTime, Time
|
||||||
- average_speed: average_speed, avg_speed, AvgSpeed
|
- average_speed: average_speed, avg_speed, AvgSpeed
|
||||||
- max_speed: max_speed, MaxSpeed, max
|
- max_speed: max_speed, MaxSpeed, max
|
||||||
@@ -22,6 +25,9 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
aliases = {
|
aliases = {
|
||||||
"lap_number": ["lap_number", "lap", "Lap", "LapNumber"],
|
"lap_number": ["lap_number", "lap", "Lap", "LapNumber"],
|
||||||
"total_laps": ["total_laps", "TotalLaps"],
|
"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"],
|
"lap_time": ["lap_time", "LapTime", "Time"],
|
||||||
"average_speed": ["average_speed", "avg_speed", "AvgSpeed"],
|
"average_speed": ["average_speed", "avg_speed", "AvgSpeed"],
|
||||||
"max_speed": ["max_speed", "MaxSpeed", "max"],
|
"max_speed": ["max_speed", "MaxSpeed", "max"],
|
||||||
@@ -53,6 +59,24 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
total_laps = 51
|
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)
|
lap_time = pick("lap_time", None)
|
||||||
if lap_time:
|
if lap_time:
|
||||||
out["lap_time"] = str(lap_time)
|
out["lap_time"] = str(lap_time)
|
||||||
@@ -97,6 +121,9 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
out.update({
|
out.update({
|
||||||
"lap_number": lap_number,
|
"lap_number": lap_number,
|
||||||
"total_laps": total_laps,
|
"total_laps": total_laps,
|
||||||
|
"position": position,
|
||||||
|
"gap_to_leader": gap_to_leader,
|
||||||
|
"gap_to_ahead": gap_to_ahead,
|
||||||
"average_speed": average_speed,
|
"average_speed": average_speed,
|
||||||
"max_speed": max_speed,
|
"max_speed": max_speed,
|
||||||
"tire_compound": tire_compound,
|
"tire_compound": tire_compound,
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import pandas as pd
|
|||||||
# {
|
# {
|
||||||
# "lap_number": 27,
|
# "lap_number": 27,
|
||||||
# "total_laps": 51,
|
# "total_laps": 51,
|
||||||
|
# "position": 5,
|
||||||
|
# "gap_to_leader": 12.345,
|
||||||
|
# "gap_to_ahead": 2.456,
|
||||||
# "lap_time": "0 days 00:01:27.318000",
|
# "lap_time": "0 days 00:01:27.318000",
|
||||||
# "average_speed": 234.62,
|
# "average_speed": 234.62,
|
||||||
# "max_speed": 333.0,
|
# "max_speed": 333.0,
|
||||||
@@ -36,6 +39,8 @@ class EnricherState:
|
|||||||
"""Maintains race state across laps for trend analysis."""
|
"""Maintains race state across laps for trend analysis."""
|
||||||
lap_times: List[float] = field(default_factory=list) # Recent lap times in seconds
|
lap_times: List[float] = field(default_factory=list) # Recent lap times in seconds
|
||||||
lap_speeds: List[float] = field(default_factory=list) # Recent average speeds
|
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_age: int = 0
|
||||||
current_tire_compound: str = "medium"
|
current_tire_compound: str = "medium"
|
||||||
tire_stint_start_lap: int = 1
|
tire_stint_start_lap: int = 1
|
||||||
@@ -63,6 +68,9 @@ class Enricher:
|
|||||||
# Extract lap data
|
# Extract lap data
|
||||||
lap_number = int(lap_data.get("lap_number", 0))
|
lap_number = int(lap_data.get("lap_number", 0))
|
||||||
total_laps = int(lap_data.get("total_laps", 51))
|
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")
|
lap_time_str = lap_data.get("lap_time")
|
||||||
average_speed = float(lap_data.get("average_speed", 0.0))
|
average_speed = float(lap_data.get("average_speed", 0.0))
|
||||||
max_speed = float(lap_data.get("max_speed", 0.0))
|
max_speed = float(lap_data.get("max_speed", 0.0))
|
||||||
@@ -77,6 +85,8 @@ class Enricher:
|
|||||||
# Update state
|
# Update state
|
||||||
self.state.lap_times.append(lap_time_seconds)
|
self.state.lap_times.append(lap_time_seconds)
|
||||||
self.state.lap_speeds.append(average_speed)
|
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_age = tire_life_laps
|
||||||
self.state.current_tire_compound = tire_compound
|
self.state.current_tire_compound = tire_compound
|
||||||
self.state.total_laps = total_laps
|
self.state.total_laps = total_laps
|
||||||
@@ -85,6 +95,8 @@ class Enricher:
|
|||||||
if len(self.state.lap_times) > 10:
|
if len(self.state.lap_times) > 10:
|
||||||
self.state.lap_times = 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.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)
|
# Set baseline (best lap time)
|
||||||
if self._baseline_lap_time is None or lap_time_seconds < self._baseline_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)
|
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)
|
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)
|
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
|
# Build enriched telemetry
|
||||||
enriched_telemetry = {
|
enriched_telemetry = {
|
||||||
@@ -104,7 +118,9 @@ class Enricher:
|
|||||||
"pace_trend": pace_trend,
|
"pace_trend": pace_trend,
|
||||||
"tire_cliff_risk": round(tire_cliff_risk, 3),
|
"tire_cliff_risk": round(tire_cliff_risk, 3),
|
||||||
"optimal_pit_window": pit_window,
|
"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
|
# Build race context
|
||||||
@@ -118,10 +134,12 @@ class Enricher:
|
|||||||
},
|
},
|
||||||
"driver_state": {
|
"driver_state": {
|
||||||
"driver_name": "Alonso",
|
"driver_name": "Alonso",
|
||||||
"current_position": 5, # Mock - could be passed in
|
"current_position": position,
|
||||||
"current_tire_compound": tire_compound,
|
"current_tire_compound": tire_compound,
|
||||||
"tire_age_laps": tire_life_laps,
|
"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
|
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:
|
def _estimate_fuel(self, current_lap: int, total_laps: int) -> float:
|
||||||
"""Estimate remaining fuel percentage based on lap progression."""
|
"""Estimate remaining fuel percentage based on lap progression."""
|
||||||
return max(0.0, 100.0 * (1.0 - (current_lap / total_laps)))
|
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
|
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,0 days 00:01:33.340000,210.17,326.0,MEDIUM,1,42.5,False
|
1,51,11,6.223,0.62,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
|
2,51,11,8.229,0.712,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
|
3,51,11,9.799,0.898,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
|
4,51,11,10.953,0.919,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
|
5,51,11,11.563,0.917,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
|
6,51,11,12.123,0.923,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
|
7,51,11,12.694,0.387,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
|
8,51,10,13.413,2.76,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
|
9,51,10,14.32,3.387,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
|
10,51,10,15.177,3.76,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
|
11,51,10,15.829,3.984,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
|
12,51,10,16.76,4.129,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
|
13,51,10,17.031,3.995,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
|
14,51,10,17.666,4.076,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
|
15,51,10,17.366,0.469,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
|
16,51,9,17.812,2.844,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
|
17,51,9,18.235,2.415,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
|
18,51,9,19.237,2.411,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
|
19,51,9,20.117,2.28,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
|
20,51,7,17.552,2.734,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
|
21,51,6,16.826,4.269,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
|
22,51,12,29.113,8.135,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
|
23,51,12,27.412,4.623,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
|
24,51,11,27.05,4.191,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
|
25,51,11,27.953,4.679,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
|
26,51,10,28.959,5.184,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
|
27,51,10,30.124,5.602,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
|
28,51,9,31.413,5.714,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
|
29,51,10,32.939,1.141,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
|
30,51,10,34.456,1.716,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
|
31,51,10,35.802,2.16,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
|
32,51,10,37.139,2.287,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
|
33,51,10,38.668,2.472,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
|
34,51,10,39.548,2.799,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
|
35,51,10,40.25,3.3,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
|
36,51,10,41.109,3.552,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
|
37,51,10,41.806,3.933,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
|
38,51,10,42.795,4.233,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
|
39,51,10,44.016,4.691,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
|
40,51,10,44.725,4.643,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
|
41,51,9,45.761,3.957,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
|
42,51,9,46.252,4.144,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
|
43,51,9,46.363,4.156,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
|
44,51,9,46.767,3.159,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
|
45,51,9,47.128,2.193,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
|
46,51,9,47.396,1.629,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
|
47,51,9,48.171,0.655,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
|
48,51,9,48.119,1.105,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
|
49,51,9,47.457,0.689,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
|
50,51,9,46.424,0.875,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
|
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 {
|
return {
|
||||||
"lap_number": int(row["lap_number"]),
|
"lap_number": int(row["lap_number"]),
|
||||||
"total_laps": int(row["total_laps"]),
|
"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"]),
|
"lap_time": str(row["lap_time"]),
|
||||||
"average_speed": float(row["average_speed"]),
|
"average_speed": float(row["average_speed"]),
|
||||||
"max_speed": float(row["max_speed"]),
|
"max_speed": float(row["max_speed"]),
|
||||||
|
|||||||
Reference in New Issue
Block a user