NetZero-Nav / netzero_nav /server.py
Aryanshh
feat: Full OpenEnv spec compliance — /metadata, /schema, /health, /mcp endpoints + pyproject.toml fix + deterministic 0-1 graders + proper inference.py STDOUT
a95dc70
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
}