from fastapi import FastAPI from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from typing import Optional import os from netzero_nav.env import NetZeroEnv from netzero_nav.models import Action, Observation, StepResponse app = FastAPI(title="NetZero Nav: Eco-Resilient Logistics", version="1.0.0") _env = NetZeroEnv() # Mount Static Dashboard static_path = os.path.join(os.getcwd(), "dashboard") app.mount("/dashboard", StaticFiles(directory=static_path), name="dashboard") @app.get("/") def root(): return RedirectResponse(url="/dashboard/index.html") class ResetRequest(BaseModel): task: Optional[str] = "easy" seed: Optional[int] = 42 # --- OpenEnv Required Endpoints --- @app.get("/health") def health(): return {"status": "healthy"} @app.get("/metadata") def metadata(): return { "name": "netzero-nav", "description": "Autonomous RL logistics agent navigating global disruptions with a Net-Zero carbon mandate.", "version": "1.0.0", "tasks": ["easy", "medium", "hard"], "tags": ["logistics", "esg", "supply-chain", "rl"] } @app.get("/schema") def schema(): return { "action": { "action_type": {"type": "string", "enum": ["order_parts", "produce", "offset", "skip", "cancel"]}, "part_type": {"type": "string", "optional": True}, "quantity": {"type": "integer", "optional": True}, "mode": {"type": "string", "enum": ["sea", "air", "rail", "road"], "optional": True}, "product": {"type": "string", "optional": True}, "offset_amount": {"type": "number", "optional": True}, "shipment_id": {"type": "string", "optional": True} }, "observation": { "step": {"type": "integer"}, "inventory": {"type": "object"}, "active_shipments": {"type": "array"}, "pending_orders": {"type": "array"}, "carbon_total": {"type": "number"}, "carbon_quota": {"type": "number"}, "cash_balance": {"type": "number"}, "news": {"type": "string", "optional": True} }, "state": { "step": {"type": "integer"}, "inventory": {"type": "object"}, "carbon_total": {"type": "number"}, "cash_balance": {"type": "number"}, "orders": {"type": "array"}, "active_shipments": {"type": "array"} } } @app.post("/mcp") def mcp(body: dict = None): return { "jsonrpc": "2.0", "id": (body or {}).get("id"), "result": { "tools": ["reset", "step", "state", "metadata", "schema"], "env": "netzero-nav" } } # --- Core Env Endpoints --- @app.post("/reset", response_model=Observation) def reset(req: ResetRequest = None): if req is None: req = ResetRequest() _env.task = req.task or "easy" return _env.reset(seed=req.seed) @app.post("/step", response_model=StepResponse) def step(action: Action): obs, reward, done, info = _env.step(action) return StepResponse(observation=obs, reward=reward, done=done, info=info) @app.post("/trigger") def trigger(): _env.sea_blocked_until = _env.step_count + 7 return {"status": "Suez Jam Triggered", "duration": 7} @app.get("/state") def state(): return { "step": _env.step_count, "inventory": _env.inventory, "carbon": _env.carbon_total, "cash": _env.cash_balance, "orders": _env.pending_orders, "active_shipments": _env.active_shipments }