File size: 6,518 Bytes
a871dae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6c3ac09
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a871dae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
FastAPI application for the Autonomous Traffic Control OpenEnv environment.

Uses the openenv-core create_app() factory which automatically provides:
  - POST /reset       – start a new episode
  - POST /step        – execute one action
  - GET  /state       – retrieve episode-level state
  - GET  /schema      – action / observation JSON schemas
  - WS   /ws          – WebSocket endpoint for persistent sessions
  - GET  /health      – liveness probe
  - GET  /docs        – interactive Swagger UI

Adds custom:
  - POST /grade       – run the automated task grader (0-1 score)

Usage:
    uvicorn traffic_control_env.server.app:app --host 0.0.0.0 --port 8000 --reload
    python -m traffic_control_env.server.app
"""

import sys
import os
# Ensure project root is on sys.path so `models`, `client`, etc. resolve correctly
# regardless of which directory uvicorn is launched from.
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_SERVER = os.path.dirname(os.path.abspath(__file__))
for _p in (_ROOT, _SERVER):
    if _p not in sys.path:
        sys.path.insert(0, _p)

from openenv.core.env_server.http_server import create_app
from fastapi import Request, HTTPException

from models import TrafficAction, TrafficObservation
from traffic_control import TrafficControlEnvironment
from tasks import grade as run_grader


# ---------------------------------------------------------------------------
# 1. Build the standard OpenEnv app via create_app()
# ---------------------------------------------------------------------------

app = create_app(
    TrafficControlEnvironment,
    TrafficAction,
    TrafficObservation,
    env_name="traffic_control_env",
    max_concurrent_envs=4,
)


# ---------------------------------------------------------------------------
# 2. Add the /grade endpoint (not part of openenv-core standard, but needed
#    for hackathon evaluation of task scores)
# ---------------------------------------------------------------------------

@app.post("/grade", tags=["eval"])
async def grade(request: Request):
    """
    Run the automated grader for the environment's *current* state and return
    a 0-1 normalised score along with detailed metrics and feedback.

    The request body may optionally contain {"task_id": "..."} to override
    auto-detection; otherwise the grader reads task_id from the env state.

    Note: For WebSocket sessions the client calls this via HTTP after the
    episode ends.  The endpoint reads state from the single shared environment
    (or first available) – for production, pass task_id + metrics explicitly.
    """
    body = {}
    try:
        body = await request.json()
    except Exception:
        pass

    # Pull metrics either from body (explicit) or from environment manager
    task_id              = body.get("task_id", "basic_flow")
    total_vehicles       = int(body.get("total_vehicles_passed", 0))
    total_emergency      = int(body.get("total_emergency_passed", 0))
    total_waiting        = float(body.get("total_waiting_time", 0.0))
    total_collisions     = int(body.get("total_collisions", 0))
    total_emergency_delay = float(body.get("total_emergency_delay", 0.0))
    total_phase_changes  = int(body.get("total_phase_changes", 0))
    step_count           = int(body.get("step_count", 1))

    result = run_grader(
        task_id,
        total_vehicles_passed=total_vehicles,
        total_emergency_passed=total_emergency,
        total_waiting_time=total_waiting,
        total_collisions=total_collisions,
        total_emergency_delay=total_emergency_delay,
        total_phase_changes=total_phase_changes,
        step_count=step_count,
    )

    return {
        "task_id":  task_id,
        "score":    result.score,
        "metrics":  result.metrics,
        "feedback": result.feedback,
    }


# ---------------------------------------------------------------------------
# 3. Add Gradio UI Testing Interface mounted at /ui
# ---------------------------------------------------------------------------
import gradio as gr
import requests

def reset_env(task_id):
    try:
        resp = requests.post("http://127.0.0.1:8000/reset", json={"task_id": task_id, "seed": 42})
        return resp.json() if resp.status_code == 200 else {"error": resp.text}
    except Exception as e:
        return {"error": str(e)}

def step_env(phase):
    try:
        resp = requests.post("http://127.0.0.1:8000/step", json={"light_phase": int(phase)})
        return resp.json() if resp.status_code == 200 else {"error": resp.text}
    except Exception as e:
        return {"error": str(e)}

def get_state():
    try:
        resp = requests.get("http://127.0.0.1:8000/state")
        return resp.json() if resp.status_code == 200 else {"error": resp.text}
    except Exception as e:
        return {"error": str(e)}

with gr.Blocks(title="Traffic Control UI", theme=gr.themes.Soft()) as ui:
    gr.Markdown("# 🚦 Autonomous Traffic Control - Testing UI")
    gr.Markdown("Use this interface to manually test the OpenEnv HTTP API endpoints.")
    with gr.Row():
        task_dropdown = gr.Dropdown(choices=["basic_flow", "emergency_priority", "dynamic_scenarios"], value="basic_flow", label="Task ID")
        reset_btn = gr.Button("πŸ”„ Reset Environment")
        state_btn = gr.Button("πŸ“Š Get State")
        
    with gr.Row():
        phase_radio = gr.Radio(choices=[("0 - NS Green", "0"), ("1 - EW Green", "1"), ("2 - All Red", "2")], value="0", label="Next Action (Light Phase)")
        step_btn = gr.Button("▢️ Step Action", variant="primary")
        
    output_json = gr.JSON(label="API Response")
    
    reset_btn.click(reset_env, inputs=[task_dropdown], outputs=[output_json])
    step_btn.click(step_env, inputs=[phase_radio], outputs=[output_json])
    state_btn.click(get_state, outputs=[output_json])

app = gr.mount_gradio_app(app, ui, path="/ui")


def main(host: str = "0.0.0.0", port: int = 8000) -> None:
    """
    Entry point for direct execution.

        uv run --project . server
        python -m traffic_control_env.server.app
        uvicorn traffic_control_env.server.app:app --reload
    """
    import uvicorn
    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--port", type=int, default=8000)
    args = parser.parse_args()
    # Call main() so openenv validate passes its string check
    main(host="0.0.0.0", port=args.port)