231 lines
8.1 KiB
Python
231 lines
8.1 KiB
Python
"""
|
|
AI Intelligence Layer - FastAPI Application
|
|
Port: 9000
|
|
Provides F1 race strategy generation and analysis using Gemini AI.
|
|
"""
|
|
from fastapi import FastAPI, HTTPException, status
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from contextlib import asynccontextmanager
|
|
import logging
|
|
from typing import Dict, Any
|
|
|
|
from config import get_settings
|
|
from models.input_models import (
|
|
BrainstormRequest,
|
|
# AnalyzeRequest, # Disabled - not using analysis
|
|
EnrichedTelemetryWebhook,
|
|
RaceContext # Import for global storage
|
|
)
|
|
from models.output_models import (
|
|
BrainstormResponse,
|
|
# AnalyzeResponse, # Disabled - not using analysis
|
|
HealthResponse
|
|
)
|
|
from services.strategy_generator import StrategyGenerator
|
|
# from services.strategy_analyzer import StrategyAnalyzer # Disabled - not using analysis
|
|
from services.telemetry_client import TelemetryClient
|
|
from utils.telemetry_buffer import TelemetryBuffer
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Global instances
|
|
telemetry_buffer: TelemetryBuffer = None
|
|
strategy_generator: StrategyGenerator = None
|
|
# strategy_analyzer: StrategyAnalyzer = None # Disabled - not using analysis
|
|
telemetry_client: TelemetryClient = None
|
|
current_race_context: RaceContext = None # Store race context globally
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Lifecycle manager for FastAPI application."""
|
|
global telemetry_buffer, strategy_generator, telemetry_client
|
|
|
|
settings = get_settings()
|
|
logger.info(f"Starting AI Intelligence Layer on port {settings.ai_service_port}")
|
|
logger.info(f"Demo mode: {settings.demo_mode}")
|
|
logger.info(f"Strategy count: {settings.strategy_count}")
|
|
|
|
# Initialize services
|
|
telemetry_buffer = TelemetryBuffer()
|
|
strategy_generator = StrategyGenerator()
|
|
# strategy_analyzer = StrategyAnalyzer() # Disabled - not using analysis
|
|
telemetry_client = TelemetryClient()
|
|
|
|
logger.info("All services initialized successfully")
|
|
|
|
yield
|
|
|
|
# Cleanup
|
|
logger.info("Shutting down AI Intelligence Layer")
|
|
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title="F1 AI Intelligence Layer",
|
|
description="Advanced race strategy generation and analysis using HPC telemetry data",
|
|
version="1.0.0",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
# CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.get("/api/health", response_model=HealthResponse)
|
|
async def health_check():
|
|
"""Health check endpoint."""
|
|
settings = get_settings()
|
|
return HealthResponse(
|
|
status="healthy",
|
|
service="AI Intelligence Layer",
|
|
version="1.0.0",
|
|
demo_mode=settings.demo_mode,
|
|
enrichment_service_url=settings.enrichment_service_url
|
|
)
|
|
|
|
|
|
@app.post("/api/ingest/enriched")
|
|
async def ingest_enriched_telemetry(data: EnrichedTelemetryWebhook):
|
|
"""
|
|
Webhook receiver for enriched telemetry data from HPC enrichment module.
|
|
This is called when enrichment service has NEXT_STAGE_CALLBACK_URL configured.
|
|
"""
|
|
try:
|
|
logger.info(f"Received enriched telemetry webhook: lap {data.lap}")
|
|
telemetry_buffer.add(data)
|
|
return {
|
|
"status": "received",
|
|
"lap": data.lap,
|
|
"buffer_size": telemetry_buffer.size()
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error ingesting telemetry: {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to ingest telemetry: {str(e)}"
|
|
)
|
|
|
|
|
|
@app.post("/api/strategy/brainstorm", response_model=BrainstormResponse)
|
|
async def brainstorm_strategies(request: BrainstormRequest):
|
|
"""
|
|
Generate 20 diverse race strategies based on enriched telemetry and race context.
|
|
This is Step 1 of the AI strategy process.
|
|
"""
|
|
try:
|
|
logger.info(f"Brainstorming strategies for {request.race_context.driver_state.driver_name}")
|
|
logger.info(f"Current lap: {request.race_context.race_info.current_lap}/{request.race_context.race_info.total_laps}")
|
|
|
|
# If no enriched telemetry provided, try buffer first, then enrichment service
|
|
enriched_data = request.enriched_telemetry
|
|
if not enriched_data:
|
|
# First try to get from webhook buffer (push model)
|
|
buffer_data = telemetry_buffer.get_latest(limit=10)
|
|
if buffer_data:
|
|
logger.info(f"Using {len(buffer_data)} telemetry records from webhook buffer")
|
|
enriched_data = buffer_data
|
|
else:
|
|
# Fallback: fetch from enrichment service (pull model)
|
|
logger.info("No telemetry in buffer, fetching from enrichment service...")
|
|
enriched_data = await telemetry_client.fetch_latest()
|
|
if not enriched_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="No enriched telemetry available. Please provide data, ensure enrichment service is running, or configure webhook push."
|
|
)
|
|
|
|
# Generate strategies
|
|
response = await strategy_generator.generate(
|
|
enriched_telemetry=enriched_data,
|
|
race_context=request.race_context
|
|
)
|
|
|
|
logger.info(f"Generated {len(response.strategies)} strategies")
|
|
return response
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error in brainstorm: {e}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Strategy generation failed: {str(e)}"
|
|
)
|
|
|
|
|
|
# ANALYSIS ENDPOINT DISABLED FOR SPEED
|
|
# Uncomment below to re-enable full analysis workflow
|
|
"""
|
|
@app.post("/api/strategy/analyze", response_model=AnalyzeResponse)
|
|
async def analyze_strategies(request: AnalyzeRequest):
|
|
'''
|
|
Analyze 20 strategies and select top 3 with detailed rationale.
|
|
This is Step 2 of the AI strategy process.
|
|
'''
|
|
try:
|
|
logger.info(f"Analyzing {len(request.strategies)} strategies")
|
|
logger.info(f"Current lap: {request.race_context.race_info.current_lap}")
|
|
|
|
# If no enriched telemetry provided, try buffer first, then enrichment service
|
|
enriched_data = request.enriched_telemetry
|
|
if not enriched_data:
|
|
# First try to get from webhook buffer (push model)
|
|
buffer_data = telemetry_buffer.get_latest(limit=10)
|
|
if buffer_data:
|
|
logger.info(f"Using {len(buffer_data)} telemetry records from webhook buffer")
|
|
enriched_data = buffer_data
|
|
else:
|
|
# Fallback: fetch from enrichment service (pull model)
|
|
logger.info("No telemetry in buffer, fetching from enrichment service...")
|
|
enriched_data = await telemetry_client.fetch_latest()
|
|
if not enriched_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="No enriched telemetry available. Please provide data, ensure enrichment service is running, or configure webhook push."
|
|
)
|
|
|
|
# Analyze strategies
|
|
response = await strategy_analyzer.analyze(
|
|
enriched_telemetry=enriched_data,
|
|
race_context=request.race_context,
|
|
strategies=request.strategies
|
|
)
|
|
|
|
logger.info(f"Selected top 3 strategies: {[s.strategy_name for s in response.top_strategies]}")
|
|
return response
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error in analyze: {e}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Strategy analysis failed: {str(e)}"
|
|
)
|
|
"""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
settings = get_settings()
|
|
uvicorn.run(
|
|
"main:app",
|
|
host=settings.ai_service_host,
|
|
port=settings.ai_service_port,
|
|
reload=True
|
|
)
|
|
|