"""REST API for the Decidron network simulator.""" from __future__ import annotations from fastapi import APIRouter, HTTPException from pydantic import BaseModel from app.decidron.engine import engine from app.decidron.diagrams import to_vis_network from app.decidron.models import ProcessingCommand, RunResult, SensorInput router = APIRouter(prefix="/decidron", tags=["decidron"]) def _network_payload() -> dict: nodes = engine.network.nodes_list() return { "version": engine.network.version, "nodes": [n.model_dump() for n in nodes], "edges": [e.model_dump(by_alias=True) for e in engine.network.edges], "diagram": to_vis_network(nodes, engine.network.edges), } @router.get("/network") async def get_network() -> dict: return _network_payload() @router.post("/sensors/input") async def submit_sensor_input(sensor_input: SensorInput) -> dict: engine.add_sensor_input(sensor_input) return { "queued": sensor_input.model_dump(), "pending_count": len(engine.pending_inputs), } @router.get("/commands") async def list_commands() -> list[ProcessingCommand]: return engine.list_commands() @router.post("/commands") async def create_command(command: ProcessingCommand) -> ProcessingCommand: return engine.add_command(command) @router.delete("/commands/{name}") async def delete_command(name: str) -> dict: if not engine.remove_command(name): raise HTTPException(status_code=404, detail=f"No command named '{name}'") return {"deleted": name} class RunRequest(BaseModel): inputs: list[SensorInput] | None = None @router.post("/run") async def run_simulation(req: RunRequest | None = None) -> dict: inputs = req.inputs if req else None results: list[RunResult] = engine.run(inputs) return { "results": [r.model_dump() for r in results], "matches": sum(1 for r in results if r.matched), "network": _network_payload(), } @router.get("/stats") async def get_stats() -> dict: return engine.stats.as_dict() @router.get("/history") async def get_history() -> dict: return { "version": engine.network.version, "snapshots": [ { "version": s.version, "reason": s.reason, "timestamp": s.timestamp, "diagram": to_vis_network(s.nodes, s.edges), } for s in engine.network.history ], } @router.post("/reset") async def reset_simulation() -> dict: engine.reset() return _network_payload()