villsim/backend/api/routes.py

304 lines
8.3 KiB
Python

"""FastAPI routes for the Village Simulation API."""
from fastapi import APIRouter, HTTPException
from backend.core.engine import get_engine, SimulationMode
from backend.core.world import WorldConfig
from backend.config import get_config, set_config, SimulationConfig, reset_config
from .schemas import (
WorldStateResponse,
ControlResponse,
SetModeRequest,
InitializeRequest,
ErrorResponse,
)
router = APIRouter()
# ============== State Endpoints ==============
@router.get(
"/state",
response_model=WorldStateResponse,
summary="Get full simulation state",
description="Returns the complete state of the simulation including agents, market, and world info.",
)
def get_state():
"""Get the full simulation state."""
engine = get_engine()
return engine.get_state()
@router.get(
"/agents",
summary="Get all agents",
description="Returns a list of all agents in the simulation.",
)
def get_agents():
"""Get all agents."""
engine = get_engine()
return {
"agents": [a.to_dict() for a in engine.world.agents],
"count": len(engine.world.agents),
"alive_count": len(engine.world.get_living_agents()),
}
@router.get(
"/agents/{agent_id}",
summary="Get a specific agent",
description="Returns detailed information about a specific agent.",
)
def get_agent(agent_id: str):
"""Get a specific agent by ID."""
engine = get_engine()
agent = engine.world.get_agent(agent_id)
if agent is None:
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
return agent.to_dict()
# ============== Market Endpoints ==============
@router.get(
"/market/orders",
summary="Get market orders",
description="Returns all active market orders.",
)
def get_market_orders():
"""Get all active market orders."""
engine = get_engine()
return engine.market.get_state_snapshot()
@router.get(
"/market/prices",
summary="Get market prices",
description="Returns current market prices for all resource types.",
)
def get_market_prices():
"""Get market price summary."""
engine = get_engine()
return engine.market.get_market_prices()
# ============== Control Endpoints ==============
@router.post(
"/control/initialize",
response_model=ControlResponse,
summary="Initialize simulation",
description="Initialize or reset the simulation with the specified parameters.",
)
def initialize_simulation(request: InitializeRequest):
"""Initialize or reset the simulation."""
engine = get_engine()
config = WorldConfig(
width=request.world_width,
height=request.world_height,
initial_agents=request.num_agents,
)
engine.reset(config)
return ControlResponse(
success=True,
message=f"Simulation initialized with {request.num_agents} agents",
turn=engine.world.current_turn,
mode=engine.mode.value,
)
@router.post(
"/control/next_step",
response_model=ControlResponse,
summary="Advance simulation",
description="Advance the simulation by one step. Only works in MANUAL mode.",
)
def next_step():
"""Advance the simulation by one step."""
engine = get_engine()
if engine.mode == SimulationMode.AUTO:
raise HTTPException(
status_code=400,
detail="Cannot manually advance while in AUTO mode. Switch to MANUAL first.",
)
if not engine.is_running:
raise HTTPException(
status_code=400,
detail="Simulation is not running. Initialize first.",
)
turn_log = engine.next_step()
return ControlResponse(
success=True,
message=f"Advanced to turn {engine.world.current_turn}",
turn=engine.world.current_turn,
mode=engine.mode.value,
)
@router.post(
"/control/mode",
response_model=ControlResponse,
summary="Set simulation mode",
description="Set the simulation mode to MANUAL or AUTO.",
)
def set_mode(request: SetModeRequest):
"""Set the simulation mode."""
engine = get_engine()
try:
mode = SimulationMode(request.mode)
engine.set_mode(mode)
return ControlResponse(
success=True,
message=f"Mode set to {mode.value}",
turn=engine.world.current_turn,
mode=mode.value,
)
except ValueError:
raise HTTPException(
status_code=400,
detail=f"Invalid mode: {request.mode}. Use 'manual' or 'auto'.",
)
@router.get(
"/control/status",
summary="Get simulation status",
description="Returns the current simulation status.",
)
def get_status():
"""Get simulation status."""
engine = get_engine()
return {
"is_running": engine.is_running,
"mode": engine.mode.value,
"current_turn": engine.world.current_turn,
"current_day": engine.world.current_day,
"time_of_day": engine.world.time_of_day.value,
"living_agents": len(engine.world.get_living_agents()),
}
# ============== Logs Endpoints ==============
@router.get(
"/logs",
summary="Get turn logs",
description="Returns the most recent turn logs.",
)
def get_logs(limit: int = 10):
"""Get recent turn logs."""
engine = get_engine()
logs = engine.turn_logs[-limit:] if engine.turn_logs else []
return {
"logs": [log.to_dict() for log in logs],
"total_turns": len(engine.turn_logs),
}
@router.get(
"/logs/{turn}",
summary="Get specific turn log",
description="Returns the log for a specific turn.",
)
def get_turn_log(turn: int):
"""Get log for a specific turn."""
engine = get_engine()
for log in engine.turn_logs:
if log.turn == turn:
return log.to_dict()
raise HTTPException(status_code=404, detail=f"Log for turn {turn} not found")
# ============== Config Endpoints ==============
@router.get(
"/config",
summary="Get simulation configuration",
description="Returns all configurable simulation parameters.",
)
def get_simulation_config():
"""Get current simulation configuration."""
config = get_config()
return config.to_dict()
@router.post(
"/config",
response_model=ControlResponse,
summary="Update simulation configuration",
description="Update simulation parameters. Requires simulation restart to take effect.",
)
def update_simulation_config(config_data: dict):
"""Update simulation configuration."""
try:
new_config = SimulationConfig.from_dict(config_data)
set_config(new_config)
return ControlResponse(
success=True,
message="Configuration updated. Restart simulation to apply changes.",
)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Invalid configuration: {str(e)}")
@router.post(
"/config/reset",
response_model=ControlResponse,
summary="Reset configuration to defaults",
description="Reset all configuration parameters to their default values.",
)
def reset_simulation_config():
"""Reset configuration to defaults."""
reset_config()
return ControlResponse(
success=True,
message="Configuration reset to defaults.",
)
@router.post(
"/config/save",
response_model=ControlResponse,
summary="Save configuration to file",
description="Save current configuration to config.json file.",
)
def save_config_to_file():
"""Save configuration to file."""
try:
config = get_config()
config.save("config.json")
return ControlResponse(
success=True,
message="Configuration saved to config.json",
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save config: {str(e)}")
@router.post(
"/config/load",
response_model=ControlResponse,
summary="Load configuration from file",
description="Load configuration from config.json file.",
)
def load_config_from_file():
"""Load configuration from file."""
try:
config = SimulationConfig.load("config.json")
set_config(config)
return ControlResponse(
success=True,
message="Configuration loaded from config.json",
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to load config: {str(e)}")