Remove all previously tracked files that are now ignored
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
# Gemini API Configuration
|
||||
GEMINI_API_KEY=AIzaSyDK_jxVlJUpzyxuiGcopSFkiqMAUD3-w0I
|
||||
GEMINI_MODEL=gemini-2.5-flash
|
||||
|
||||
# Service Configuration
|
||||
AI_SERVICE_PORT=9000
|
||||
AI_SERVICE_HOST=0.0.0.0
|
||||
|
||||
# Enrichment Service Integration
|
||||
ENRICHMENT_SERVICE_URL=http://localhost:8000
|
||||
ENRICHMENT_FETCH_LIMIT=10
|
||||
|
||||
# Demo Mode (enables caching and consistent responses for demos)
|
||||
DEMO_MODE=false
|
||||
|
||||
# Fast Mode (use shorter prompts for faster responses)
|
||||
FAST_MODE=true
|
||||
|
||||
# Strategy Generation Settings
|
||||
STRATEGY_COUNT=3 # Number of strategies to generate (3 for testing, 20 for production)
|
||||
|
||||
# Performance Settings
|
||||
BRAINSTORM_TIMEOUT=90
|
||||
ANALYZE_TIMEOUT=120
|
||||
GEMINI_MAX_RETRIES=3
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -49,6 +49,7 @@ strategy_generator: StrategyGenerator = None
|
||||
telemetry_client: TelemetryClient = None
|
||||
current_race_context: RaceContext = None # Store race context globally
|
||||
last_control_command: Dict[str, int] = {"brake_bias": 5, "differential_slip": 5} # Store last command
|
||||
strategy_history: List[Dict[str, Any]] = [] # Track past strategies for continuity
|
||||
|
||||
# WebSocket connection manager
|
||||
class ConnectionManager:
|
||||
@@ -378,7 +379,7 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
2. AI layer processes telemetry and generates strategies
|
||||
3. AI layer pushes control commands back to Pi (brake_bias, differential_slip)
|
||||
"""
|
||||
global current_race_context, last_control_command
|
||||
global current_race_context, last_control_command, strategy_history
|
||||
|
||||
vehicle_id = await websocket_manager.connect(websocket)
|
||||
|
||||
@@ -389,7 +390,11 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
# Reset last control command to neutral for new session
|
||||
last_control_command = {"brake_bias": 5, "differential_slip": 5}
|
||||
|
||||
# Clear strategy history for new race
|
||||
strategy_history = []
|
||||
|
||||
logger.info("[WebSocket] Telemetry buffer cleared for new connection")
|
||||
logger.info("[WebSocket] Strategy history cleared for new race")
|
||||
|
||||
# Notify dashboards of new vehicle connection
|
||||
await dashboard_manager.broadcast({
|
||||
@@ -454,12 +459,26 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
try:
|
||||
response = await strategy_generator.generate(
|
||||
enriched_telemetry=buffer_data,
|
||||
race_context=current_race_context
|
||||
race_context=current_race_context,
|
||||
strategy_history=strategy_history
|
||||
)
|
||||
|
||||
# Extract top strategy (first one)
|
||||
top_strategy = response.strategies[0] if response.strategies else None
|
||||
|
||||
# Add to strategy history
|
||||
if top_strategy:
|
||||
strategy_history.append({
|
||||
"lap": lap_number,
|
||||
"strategy_name": top_strategy.strategy_name,
|
||||
"risk_level": top_strategy.risk_level,
|
||||
"brief_description": top_strategy.brief_description,
|
||||
"reasoning": top_strategy.reasoning
|
||||
})
|
||||
# Keep only last 10 strategies
|
||||
if len(strategy_history) > 10:
|
||||
strategy_history.pop(0)
|
||||
|
||||
# Generate control commands based on strategy
|
||||
control_command = generate_control_command(
|
||||
lap_number=lap_number,
|
||||
@@ -490,6 +509,11 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
"type": "lap_data",
|
||||
"vehicle_id": vehicle_id,
|
||||
"lap_data": enriched,
|
||||
"race_context": {
|
||||
"position": current_race_context.driver_state.current_position,
|
||||
"gap_to_leader": current_race_context.driver_state.gap_to_leader,
|
||||
"gap_to_ahead": current_race_context.driver_state.gap_to_ahead
|
||||
},
|
||||
"control_output": {
|
||||
"brake_bias": control_command["brake_bias"],
|
||||
"differential_slip": control_command["differential_slip"]
|
||||
@@ -497,7 +521,8 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
"strategy": {
|
||||
"strategy_name": top_strategy.strategy_name,
|
||||
"risk_level": top_strategy.risk_level,
|
||||
"brief_description": top_strategy.brief_description
|
||||
"brief_description": top_strategy.brief_description,
|
||||
"reasoning": top_strategy.reasoning
|
||||
} if top_strategy else None,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
@@ -527,6 +552,11 @@ async def websocket_pi_endpoint(websocket: WebSocket):
|
||||
"type": "lap_data",
|
||||
"vehicle_id": vehicle_id,
|
||||
"lap_data": enriched,
|
||||
"race_context": {
|
||||
"position": current_race_context.driver_state.current_position,
|
||||
"gap_to_leader": current_race_context.driver_state.gap_to_leader,
|
||||
"gap_to_ahead": current_race_context.driver_state.gap_to_ahead
|
||||
},
|
||||
"control_output": {
|
||||
"brake_bias": 5,
|
||||
"differential_slip": 5
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -62,6 +62,7 @@ class Strategy(BaseModel):
|
||||
pit_laps: List[int] = Field(..., description="Lap numbers for pit stops")
|
||||
tire_sequence: List[Literal["soft", "medium", "hard", "intermediate", "wet"]] = Field(..., description="Tire compounds in order")
|
||||
brief_description: str = Field(..., description="One sentence rationale")
|
||||
reasoning: Optional[str] = Field(None, description="Detailed explanation including strategy change/continuity rationale")
|
||||
risk_level: Literal["low", "medium", "high", "critical"] = Field(..., description="Risk assessment")
|
||||
key_assumption: str = Field(..., description="Main assumption this strategy relies on")
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,8 @@ from config import get_settings
|
||||
|
||||
def build_brainstorm_prompt_fast(
|
||||
enriched_telemetry: List[EnrichedTelemetryWebhook],
|
||||
race_context: RaceContext
|
||||
race_context: RaceContext,
|
||||
strategy_history: List[dict] = None
|
||||
) -> str:
|
||||
"""Build a faster, more concise prompt for quicker responses (lap-level data)."""
|
||||
settings = get_settings()
|
||||
@@ -27,17 +28,29 @@ def build_brainstorm_prompt_fast(
|
||||
if gap_to_leader > 0 and position > 1:
|
||||
comp_info += f", {gap_to_leader:.1f}s from leader"
|
||||
|
||||
# Format strategy history (last 3 strategies)
|
||||
history_text = ""
|
||||
if strategy_history and len(strategy_history) > 0:
|
||||
recent_history = strategy_history[-3:] # Last 3 strategies
|
||||
history_lines = []
|
||||
for h in recent_history:
|
||||
history_lines.append(f"Lap {h['lap']}: {h['strategy_name']} (Risk: {h['risk_level']})")
|
||||
history_text = f"\n\nPAST STRATEGIES:\n" + "\n".join(history_lines)
|
||||
history_text += f"\n\nREQUIREMENT: If changing from previous strategy '{recent_history[-1]['strategy_name']}', explain WHY the switch is necessary. If staying with same approach, explain continuity."
|
||||
else:
|
||||
history_text = "\n\nNOTE: This is the first strategy generation - no previous strategy to compare."
|
||||
|
||||
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}, {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}, Position trend {latest.position_trend}, Competitive pressure {latest.competitive_pressure:.2f}
|
||||
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}{history_text}
|
||||
|
||||
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", "reasoning": "detailed explanation including strategy change rationale if applicable", "risk_level": "medium", "key_assumption": "main assumption"}}]}}"""
|
||||
|
||||
elif count <= 5:
|
||||
# Fast mode: 2-5 strategies with different approaches
|
||||
@@ -47,11 +60,13 @@ CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_
|
||||
|
||||
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}
|
||||
|
||||
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"})
|
||||
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"}){history_text}
|
||||
|
||||
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"}}]}}"""
|
||||
CRITICAL: Each strategy MUST include 'reasoning' field explaining the approach and, if applicable, why it differs from or continues the previous strategy.
|
||||
|
||||
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", "reasoning": "Detailed explanation of this strategy including why we're switching from/continuing previous approach based on current telemetry and competitive situation", "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}.
|
||||
|
||||
@@ -59,16 +74,19 @@ CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_
|
||||
|
||||
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}
|
||||
|
||||
COMPETITIVE: Gap ahead {gap_to_ahead:.1f}s, Position trending {latest.position_trend}
|
||||
COMPETITIVE: Gap ahead {gap_to_ahead:.1f}s, Position trending {latest.position_trend}{history_text}
|
||||
|
||||
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"}}]}}"""
|
||||
CRITICAL: Each strategy MUST include 'reasoning' field with detailed rationale and strategy continuity/change explanation.
|
||||
|
||||
JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "reasoning": "Comprehensive explanation including strategy evolution context", "risk_level": "low|medium|high|critical", "key_assumption": "main assumption"}}]}}"""
|
||||
|
||||
|
||||
def build_brainstorm_prompt(
|
||||
enriched_telemetry: List[EnrichedTelemetryWebhook],
|
||||
race_context: RaceContext
|
||||
race_context: RaceContext,
|
||||
strategy_history: List[dict] = None
|
||||
) -> str:
|
||||
"""
|
||||
Build the brainstorm prompt for Gemini.
|
||||
@@ -76,6 +94,7 @@ def build_brainstorm_prompt(
|
||||
Args:
|
||||
enriched_telemetry: Recent enriched telemetry data
|
||||
race_context: Current race context
|
||||
strategy_history: List of previous strategies for context
|
||||
|
||||
Returns:
|
||||
Formatted prompt string
|
||||
@@ -108,6 +127,14 @@ def build_brainstorm_prompt(
|
||||
"gap_seconds": round(c.gap_seconds, 1)
|
||||
})
|
||||
|
||||
# Format strategy history
|
||||
history_section = ""
|
||||
if strategy_history and len(strategy_history) > 0:
|
||||
history_section = "\n\nSTRATEGY HISTORY (Recent decisions):\n"
|
||||
for h in strategy_history[-5:]: # Last 5 strategies
|
||||
history_section += f"- Lap {h['lap']}: {h['strategy_name']} (Risk: {h['risk_level']}) - {h.get('brief_description', 'N/A')}\n"
|
||||
history_section += f"\nCONTEXT: Previous strategy was '{strategy_history[-1]['strategy_name']}'. If recommending a change, clearly explain WHY. If continuing same approach, explain the continuity.\n"
|
||||
|
||||
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:
|
||||
@@ -135,7 +162,7 @@ COMPETITORS:
|
||||
{competitors_data}
|
||||
|
||||
ENRICHED TELEMETRY (Last {len(telemetry_data)} laps, newest first):
|
||||
{telemetry_data}
|
||||
{telemetry_data}{history_section}
|
||||
|
||||
KEY INSIGHTS:
|
||||
- Latest tire degradation rate: {latest.tire_degradation_rate:.3f}
|
||||
@@ -160,9 +187,12 @@ For each strategy provide:
|
||||
- pit_laps: [array of lap numbers]
|
||||
- tire_sequence: [array of compounds: "soft", "medium", "hard"]
|
||||
- brief_description: One sentence rationale
|
||||
- reasoning: DETAILED explanation (3-5 sentences) including: (1) Why this strategy fits current conditions, (2) How it addresses telemetry trends, (3) Strategy evolution - why changing from or continuing previous approach
|
||||
- risk_level: "low", "medium", "high", or "critical"
|
||||
- key_assumption: Main assumption this strategy relies on
|
||||
|
||||
CRITICAL: The 'reasoning' field must include strategy continuity/change rationale relative to past decisions.
|
||||
|
||||
OUTPUT FORMAT (JSON only, no markdown):
|
||||
{{
|
||||
"strategies": [
|
||||
@@ -173,6 +203,7 @@ OUTPUT FORMAT (JSON only, no markdown):
|
||||
"pit_laps": [32],
|
||||
"tire_sequence": ["medium", "hard"],
|
||||
"brief_description": "Extend mediums to lap 32, safe finish on hards",
|
||||
"reasoning": "Current tire degradation at 0.45 suggests we can safely extend to lap 32 before hitting the cliff. This maintains our conservative approach from lap 3 as conditions haven't changed significantly - tire deg is stable and we're not under immediate competitive pressure. The hard tire finish provides low-risk race completion.",
|
||||
"risk_level": "low",
|
||||
"key_assumption": "Tire degradation stays below 0.85 until lap 32"
|
||||
}}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -25,7 +25,8 @@ class StrategyGenerator:
|
||||
async def generate(
|
||||
self,
|
||||
enriched_telemetry: List[EnrichedTelemetryWebhook],
|
||||
race_context: RaceContext
|
||||
race_context: RaceContext,
|
||||
strategy_history: List[dict] = None
|
||||
) -> BrainstormResponse:
|
||||
"""
|
||||
Generate 20 diverse race strategies.
|
||||
@@ -33,6 +34,7 @@ class StrategyGenerator:
|
||||
Args:
|
||||
enriched_telemetry: Recent enriched telemetry data
|
||||
race_context: Current race context
|
||||
strategy_history: List of previous strategies for continuity
|
||||
|
||||
Returns:
|
||||
BrainstormResponse with 20 strategies
|
||||
@@ -41,13 +43,15 @@ class StrategyGenerator:
|
||||
Exception: If generation fails
|
||||
"""
|
||||
logger.info(f"Generating strategies using {len(enriched_telemetry)} laps of telemetry")
|
||||
if strategy_history:
|
||||
logger.info(f"Including {len(strategy_history)} previous strategies in context")
|
||||
|
||||
# Build prompt (use fast mode if enabled)
|
||||
if self.settings.fast_mode:
|
||||
from prompts.brainstorm_prompt import build_brainstorm_prompt_fast
|
||||
prompt = build_brainstorm_prompt_fast(enriched_telemetry, race_context)
|
||||
prompt = build_brainstorm_prompt_fast(enriched_telemetry, race_context, strategy_history or [])
|
||||
else:
|
||||
prompt = build_brainstorm_prompt(enriched_telemetry, race_context)
|
||||
prompt = build_brainstorm_prompt(enriched_telemetry, race_context, strategy_history or [])
|
||||
|
||||
# Generate with Gemini (high temperature for creativity)
|
||||
response_data = await self.gemini_client.generate_json(
|
||||
|
||||
@@ -715,14 +715,14 @@
|
||||
}
|
||||
|
||||
function handleMessage(data) {
|
||||
const { type, vehicle_id, lap_data, control_output, strategy, timestamp } = data;
|
||||
const { type, vehicle_id, lap_data, race_context, control_output, strategy, timestamp } = data;
|
||||
|
||||
if (type === 'vehicle_connected') {
|
||||
addVehicle(vehicle_id, timestamp);
|
||||
} else if (type === 'vehicle_disconnected') {
|
||||
removeVehicle(vehicle_id);
|
||||
} else if (type === 'lap_data') {
|
||||
addLapData(vehicle_id, lap_data, control_output, strategy, timestamp);
|
||||
addLapData(vehicle_id, lap_data, race_context, control_output, strategy, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,7 +750,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addLapData(vehicleId, lapData, controlOutput, strategy, timestamp) {
|
||||
function addLapData(vehicleId, lapData, raceContext, controlOutput, strategy, timestamp) {
|
||||
if (!vehicles.has(vehicleId)) {
|
||||
addVehicle(vehicleId, timestamp);
|
||||
}
|
||||
@@ -758,6 +758,7 @@
|
||||
const vehicle = vehicles.get(vehicleId);
|
||||
vehicle.laps.push({
|
||||
...lapData,
|
||||
race_context: raceContext, // Add race context with position and gaps
|
||||
control_output: controlOutput,
|
||||
strategy: strategy,
|
||||
timestamp: timestamp
|
||||
@@ -907,6 +908,37 @@
|
||||
|
||||
let bodyHtml = '';
|
||||
|
||||
// Race Position section (lap_time, position, gaps)
|
||||
if (lap.race_context || lap.lap_time) {
|
||||
bodyHtml += `
|
||||
<div class="modal-section">
|
||||
<h3>Race Position</h3>
|
||||
<div class="modal-data">
|
||||
${lap.lap_time ? `
|
||||
<div class="modal-data-row">
|
||||
<span class="modal-data-label">Lap Time</span>
|
||||
<span class="modal-data-value">${lap.lap_time}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${lap.race_context ? `
|
||||
<div class="modal-data-row">
|
||||
<span class="modal-data-label">Position</span>
|
||||
<span class="modal-data-value">P${lap.race_context.position}</span>
|
||||
</div>
|
||||
<div class="modal-data-row">
|
||||
<span class="modal-data-label">Gap from Leader</span>
|
||||
<span class="modal-data-value">${lap.race_context.gap_to_leader > 0 ? '+' + lap.race_context.gap_to_leader.toFixed(3) + 's' : (lap.race_context.position === 1 ? 'Leading' : 'N/A')}</span>
|
||||
</div>
|
||||
<div class="modal-data-row">
|
||||
<span class="modal-data-label">Gap from Car Ahead</span>
|
||||
<span class="modal-data-value">${lap.race_context.gap_to_ahead > 0 ? '+' + lap.race_context.gap_to_ahead.toFixed(3) + 's' : (lap.race_context.position === 1 ? 'Leading' : 'N/A')}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Telemetry data section
|
||||
bodyHtml += `
|
||||
<div class="modal-section">
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user