""" API endpoints for simulation control. """ from typing import Dict, Optional, List from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel from sqlalchemy.orm import Session from ..models.config import SimulationConfigModel from ..models.results import ( SimulationResultsModel, QueueStatisticsModel, TimeSeriesDataModel, HistogramDataModel ) from ..core.simulation import Simulator from ..analytics.jackson import JacksonAnalyzer from ..analytics.comparison import compare_results from ..database.connection import get_db from ..models.database import SimulationRun router = APIRouter(prefix="/api/simulation", tags=["simulation"]) # In-memory storage for simulation sessions # In production, use Redis or a database simulation_sessions: Dict[str, dict] = {} class SimulationSessionResponse(BaseModel): """Response when starting a simulation.""" session_id: str status: str message: str @router.post("/start", response_model=SimulationSessionResponse) async def start_simulation(config: SimulationConfigModel, db: Session = Depends(get_db)): """ Start a new simulation with the given configuration. Args: config: Simulation configuration Returns: Session information """ try: # Convert Pydantic model to internal config internal_config = config.to_simulation_config() # Create simulator simulator = Simulator(internal_config) # Run simulation (synchronous for now - could be async/background task) results = simulator.run() # Generate session ID import uuid session_id = str(uuid.uuid4()) # Check stability (simple heuristic: max utilization) max_utilization = max( results.coordinator_stats["utilization"], max(s["utilization"] for s in results.server_stats.values()) if results.server_stats else 0 ) is_stable = max_utilization < 0.95 # Convert to QueueStatisticsModel for serialization coordinator_stats = QueueStatisticsModel( queue_id="coordinator", service_rate=results.coordinator_stats["service_rate"], total_arrivals=results.coordinator_stats["total_arrivals"], total_departures=results.coordinator_stats["total_departures"], average_wait_time=results.coordinator_stats["average_wait_time"], average_service_time=results.coordinator_stats["average_service_time"], average_system_time=results.coordinator_stats["average_system_time"], utilization=results.coordinator_stats["utilization"] ) server_stats = {} for server_id, stats in results.server_stats.items(): server_stats[server_id] = QueueStatisticsModel( queue_id=server_id, service_rate=stats["service_rate"], total_arrivals=stats["total_arrivals"], total_departures=stats["total_departures"], average_wait_time=stats["average_wait_time"], average_service_time=stats["average_service_time"], average_system_time=stats["average_system_time"], utilization=stats["utilization"] ) # Sauvegarder en base de données db_simulation = SimulationRun( id=session_id, config=config.model_dump(), total_requests_arrived=results.total_requests_arrived, total_requests_completed=results.total_requests_completed, average_system_time=results.average_system_time, average_customers_in_system=results.average_customers_in_system, is_stable=is_stable, coordinator_stats=coordinator_stats.model_dump(), server_stats={k: v.model_dump() for k, v in server_stats.items()}, time_series=results.time_series_data, processing_time_histogram=results.histogram_data, status="completed" ) db.add(db_simulation) db.commit() # Store results also in memory for backward compatibility simulation_sessions[session_id] = { "config": config.model_dump(), "results": results, "status": "completed" } return SimulationSessionResponse( session_id=session_id, status="completed", message="Simulation completed successfully" ) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @router.get("/results/{session_id}") async def get_simulation_results(session_id: str): """ Get results for a completed simulation. Args: session_id: Session identifier Returns: Simulation results """ if session_id not in simulation_sessions: raise HTTPException(status_code=404, detail="Session not found") session = simulation_sessions[session_id] results = session["results"] # Convert to response model coordinator_stats = QueueStatisticsModel( queue_id="coordinator", service_rate=results.coordinator_stats["service_rate"], total_arrivals=results.coordinator_stats["total_arrivals"], total_departures=results.coordinator_stats["total_departures"], average_wait_time=results.coordinator_stats["average_wait_time"], average_service_time=results.coordinator_stats["average_service_time"], average_system_time=results.coordinator_stats["average_system_time"], utilization=results.coordinator_stats["utilization"] ) server_stats = {} for server_id, stats in results.server_stats.items(): server_stats[server_id] = QueueStatisticsModel( queue_id=server_id, service_rate=stats["service_rate"], total_arrivals=stats["total_arrivals"], total_departures=stats["total_departures"], average_wait_time=stats["average_wait_time"], average_service_time=stats["average_service_time"], average_system_time=stats["average_system_time"], utilization=stats["utilization"] ) # Check stability (simple heuristic: max utilization) max_utilization = max( results.coordinator_stats["utilization"], max(s["utilization"] for s in results.server_stats.values()) if results.server_stats else 0 ) is_stable = max_utilization < 0.95 # Convert time series data if available time_series = None if results.time_series_data: time_series = TimeSeriesDataModel( timestamps=results.time_series_data["timestamps"], customers_in_system=results.time_series_data["customers_in_system"], customers_per_queue=results.time_series_data["customers_per_queue"], cumulative_arrivals_per_queue=results.time_series_data["cumulative_arrivals_per_queue"], cumulative_departures_per_queue=results.time_series_data["cumulative_departures_per_queue"] ) # Convert histogram data if available histogram = None if results.histogram_data: histogram = HistogramDataModel( bins=results.histogram_data["bins"], frequencies=results.histogram_data["frequencies"], min_value=results.histogram_data["min_value"], max_value=results.histogram_data["max_value"], mean=results.histogram_data["mean"], std_dev=results.histogram_data["std_dev"] ) response = SimulationResultsModel( config=session["config"], total_requests_arrived=results.total_requests_arrived, total_requests_completed=results.total_requests_completed, average_system_time=results.average_system_time, average_customers_in_system=results.average_customers_in_system, coordinator_stats=coordinator_stats, server_stats=server_stats, time_series=time_series, processing_time_histogram=histogram, is_stable=is_stable, stability_notes="High utilization detected" if not is_stable else None ) return response @router.delete("/results/{session_id}") async def delete_simulation_results(session_id: str): """ Delete simulation results. Args: session_id: Session identifier Returns: Confirmation message """ if session_id not in simulation_sessions: raise HTTPException(status_code=404, detail="Session not found") del simulation_sessions[session_id] return {"message": "Session deleted successfully"} @router.get("/simulations") async def list_simulations( skip: int = 0, limit: int = 50, db: Session = Depends(get_db) ): """ Liste toutes les simulations sauvegardées avec pagination. Args: skip: Nombre de simulations à ignorer limit: Nombre maximum de simulations à retourner db: Session de base de données Returns: Liste des simulations avec leurs métadonnées """ simulations = db.query(SimulationRun)\ .order_by(SimulationRun.created_at.desc())\ .offset(skip)\ .limit(limit)\ .all() return { "simulations": [ { "session_id": sim.id, "created_at": sim.created_at.isoformat(), "total_requests": sim.total_requests_completed, "is_stable": sim.is_stable, "arrival_rate": sim.config["arrival_rate"], } for sim in simulations ], "count": len(simulations) } @router.post("/compare") async def compare_simulations( session_ids: List[str], db: Session = Depends(get_db) ): """ Compare plusieurs simulations sélectionnées. Args: session_ids: Liste des IDs de simulations à comparer db: Session de base de données Returns: Données de comparaison des simulations """ simulations = db.query(SimulationRun)\ .filter(SimulationRun.id.in_(session_ids))\ .all() if len(simulations) != len(session_ids): raise HTTPException(status_code=404, detail="Some sessions not found") comparison = [] for sim in simulations: comparison.append({ "session_id": sim.id, "created_at": sim.created_at.isoformat(), "config": sim.config, "total_requests_completed": sim.total_requests_completed, "average_system_time": sim.average_system_time, "coordinator_stats": sim.coordinator_stats, "server_stats": sim.server_stats, "time_series": sim.time_series, }) return {"simulations": comparison} @router.get("/sessions") async def list_simulation_sessions(): """ List all simulation sessions. Returns: List of session IDs and their status """ sessions = [] for session_id, session in simulation_sessions.items(): sessions.append({ "session_id": session_id, "status": session["status"], "total_requests": session["results"].total_requests_completed }) return {"sessions": sessions, "count": len(sessions)}