Remove all previously tracked files that are now ignored

This commit is contained in:
Aditya Pulipaka
2025-10-19 05:29:30 -05:00
parent 1773010712
commit 89924c1580
45 changed files with 117 additions and 43 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.env .env
__pycache__/
*.pyc *.pyc

View File

@@ -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

View File

@@ -49,6 +49,7 @@ strategy_generator: StrategyGenerator = None
telemetry_client: TelemetryClient = None telemetry_client: TelemetryClient = None
current_race_context: RaceContext = None # Store race context globally current_race_context: RaceContext = None # Store race context globally
last_control_command: Dict[str, int] = {"brake_bias": 5, "differential_slip": 5} # Store last command 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 # WebSocket connection manager
class ConnectionManager: class ConnectionManager:
@@ -378,7 +379,7 @@ async def websocket_pi_endpoint(websocket: WebSocket):
2. AI layer processes telemetry and generates strategies 2. AI layer processes telemetry and generates strategies
3. AI layer pushes control commands back to Pi (brake_bias, differential_slip) 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) 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 # Reset last control command to neutral for new session
last_control_command = {"brake_bias": 5, "differential_slip": 5} 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] Telemetry buffer cleared for new connection")
logger.info("[WebSocket] Strategy history cleared for new race")
# Notify dashboards of new vehicle connection # Notify dashboards of new vehicle connection
await dashboard_manager.broadcast({ await dashboard_manager.broadcast({
@@ -454,12 +459,26 @@ async def websocket_pi_endpoint(websocket: WebSocket):
try: try:
response = await strategy_generator.generate( response = await strategy_generator.generate(
enriched_telemetry=buffer_data, enriched_telemetry=buffer_data,
race_context=current_race_context race_context=current_race_context,
strategy_history=strategy_history
) )
# Extract top strategy (first one) # Extract top strategy (first one)
top_strategy = response.strategies[0] if response.strategies else None 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 # Generate control commands based on strategy
control_command = generate_control_command( control_command = generate_control_command(
lap_number=lap_number, lap_number=lap_number,
@@ -490,6 +509,11 @@ async def websocket_pi_endpoint(websocket: WebSocket):
"type": "lap_data", "type": "lap_data",
"vehicle_id": vehicle_id, "vehicle_id": vehicle_id,
"lap_data": enriched, "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": { "control_output": {
"brake_bias": control_command["brake_bias"], "brake_bias": control_command["brake_bias"],
"differential_slip": control_command["differential_slip"] "differential_slip": control_command["differential_slip"]
@@ -497,7 +521,8 @@ async def websocket_pi_endpoint(websocket: WebSocket):
"strategy": { "strategy": {
"strategy_name": top_strategy.strategy_name, "strategy_name": top_strategy.strategy_name,
"risk_level": top_strategy.risk_level, "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, } if top_strategy else None,
"timestamp": datetime.now().isoformat() "timestamp": datetime.now().isoformat()
}) })
@@ -527,6 +552,11 @@ async def websocket_pi_endpoint(websocket: WebSocket):
"type": "lap_data", "type": "lap_data",
"vehicle_id": vehicle_id, "vehicle_id": vehicle_id,
"lap_data": enriched, "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": { "control_output": {
"brake_bias": 5, "brake_bias": 5,
"differential_slip": 5 "differential_slip": 5

View File

@@ -62,6 +62,7 @@ class Strategy(BaseModel):
pit_laps: List[int] = Field(..., description="Lap numbers for pit stops") 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") tire_sequence: List[Literal["soft", "medium", "hard", "intermediate", "wet"]] = Field(..., description="Tire compounds in order")
brief_description: str = Field(..., description="One sentence rationale") 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") risk_level: Literal["low", "medium", "high", "critical"] = Field(..., description="Risk assessment")
key_assumption: str = Field(..., description="Main assumption this strategy relies on") key_assumption: str = Field(..., description="Main assumption this strategy relies on")

View File

@@ -9,7 +9,8 @@ from config import get_settings
def build_brainstorm_prompt_fast( def build_brainstorm_prompt_fast(
enriched_telemetry: List[EnrichedTelemetryWebhook], enriched_telemetry: List[EnrichedTelemetryWebhook],
race_context: RaceContext race_context: RaceContext,
strategy_history: List[dict] = None
) -> str: ) -> str:
"""Build a faster, more concise prompt for quicker responses (lap-level data).""" """Build a faster, more concise prompt for quicker responses (lap-level data)."""
settings = get_settings() settings = get_settings()
@@ -27,17 +28,29 @@ def build_brainstorm_prompt_fast(
if gap_to_leader > 0 and position > 1: if gap_to_leader > 0 and position > 1:
comp_info += f", {gap_to_leader:.1f}s from leader" 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: 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}, {comp_info}, {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}, 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. 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: elif count <= 5:
# Fast mode: 2-5 strategies with different approaches # 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} 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. 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}. 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} 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. 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( def build_brainstorm_prompt(
enriched_telemetry: List[EnrichedTelemetryWebhook], enriched_telemetry: List[EnrichedTelemetryWebhook],
race_context: RaceContext race_context: RaceContext,
strategy_history: List[dict] = None
) -> str: ) -> str:
""" """
Build the brainstorm prompt for Gemini. Build the brainstorm prompt for Gemini.
@@ -76,6 +94,7 @@ def build_brainstorm_prompt(
Args: Args:
enriched_telemetry: Recent enriched telemetry data enriched_telemetry: Recent enriched telemetry data
race_context: Current race context race_context: Current race context
strategy_history: List of previous strategies for context
Returns: Returns:
Formatted prompt string Formatted prompt string
@@ -108,6 +127,14 @@ def build_brainstorm_prompt(
"gap_seconds": round(c.gap_seconds, 1) "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. 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:
@@ -135,7 +162,7 @@ COMPETITORS:
{competitors_data} {competitors_data}
ENRICHED TELEMETRY (Last {len(telemetry_data)} laps, newest first): ENRICHED TELEMETRY (Last {len(telemetry_data)} laps, newest first):
{telemetry_data} {telemetry_data}{history_section}
KEY INSIGHTS: KEY INSIGHTS:
- Latest tire degradation rate: {latest.tire_degradation_rate:.3f} - Latest tire degradation rate: {latest.tire_degradation_rate:.3f}
@@ -160,9 +187,12 @@ For each strategy provide:
- pit_laps: [array of lap numbers] - pit_laps: [array of lap numbers]
- tire_sequence: [array of compounds: "soft", "medium", "hard"] - tire_sequence: [array of compounds: "soft", "medium", "hard"]
- brief_description: One sentence rationale - 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" - risk_level: "low", "medium", "high", or "critical"
- key_assumption: Main assumption this strategy relies on - 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): OUTPUT FORMAT (JSON only, no markdown):
{{ {{
"strategies": [ "strategies": [
@@ -173,6 +203,7 @@ OUTPUT FORMAT (JSON only, no markdown):
"pit_laps": [32], "pit_laps": [32],
"tire_sequence": ["medium", "hard"], "tire_sequence": ["medium", "hard"],
"brief_description": "Extend mediums to lap 32, safe finish on hards", "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", "risk_level": "low",
"key_assumption": "Tire degradation stays below 0.85 until lap 32" "key_assumption": "Tire degradation stays below 0.85 until lap 32"
}} }}

View File

@@ -25,7 +25,8 @@ class StrategyGenerator:
async def generate( async def generate(
self, self,
enriched_telemetry: List[EnrichedTelemetryWebhook], enriched_telemetry: List[EnrichedTelemetryWebhook],
race_context: RaceContext race_context: RaceContext,
strategy_history: List[dict] = None
) -> BrainstormResponse: ) -> BrainstormResponse:
""" """
Generate 20 diverse race strategies. Generate 20 diverse race strategies.
@@ -33,6 +34,7 @@ class StrategyGenerator:
Args: Args:
enriched_telemetry: Recent enriched telemetry data enriched_telemetry: Recent enriched telemetry data
race_context: Current race context race_context: Current race context
strategy_history: List of previous strategies for continuity
Returns: Returns:
BrainstormResponse with 20 strategies BrainstormResponse with 20 strategies
@@ -41,13 +43,15 @@ class StrategyGenerator:
Exception: If generation fails Exception: If generation fails
""" """
logger.info(f"Generating strategies using {len(enriched_telemetry)} laps of telemetry") 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) # Build prompt (use fast mode if enabled)
if self.settings.fast_mode: if self.settings.fast_mode:
from prompts.brainstorm_prompt import build_brainstorm_prompt_fast 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: 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) # Generate with Gemini (high temperature for creativity)
response_data = await self.gemini_client.generate_json( response_data = await self.gemini_client.generate_json(

View File

@@ -715,14 +715,14 @@
} }
function handleMessage(data) { 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') { if (type === 'vehicle_connected') {
addVehicle(vehicle_id, timestamp); addVehicle(vehicle_id, timestamp);
} else if (type === 'vehicle_disconnected') { } else if (type === 'vehicle_disconnected') {
removeVehicle(vehicle_id); removeVehicle(vehicle_id);
} else if (type === 'lap_data') { } 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)) { if (!vehicles.has(vehicleId)) {
addVehicle(vehicleId, timestamp); addVehicle(vehicleId, timestamp);
} }
@@ -758,6 +758,7 @@
const vehicle = vehicles.get(vehicleId); const vehicle = vehicles.get(vehicleId);
vehicle.laps.push({ vehicle.laps.push({
...lapData, ...lapData,
race_context: raceContext, // Add race context with position and gaps
control_output: controlOutput, control_output: controlOutput,
strategy: strategy, strategy: strategy,
timestamp: timestamp timestamp: timestamp
@@ -907,6 +908,37 @@
let bodyHtml = ''; 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 // Telemetry data section
bodyHtml += ` bodyHtml += `
<div class="modal-section"> <div class="modal-section">