""" FastAPI application for the OASIS Environment. Uses OpenEnv's create_app factory to generate standard endpoints: - POST /reset : Reset the environment (accepts task_id in body) - POST /step : Execute an insulin dosing action - GET /state : Get current environment state - GET /health : Health check - GET /schema : Action/observation JSON schemas - WS /ws : WebSocket for persistent sessions Additional custom endpoints: - GET /tasks : List all 3 tasks with descriptions - GET /healthz : Alias health check Usage: uvicorn server.app:app --host 0.0.0.0 --port 8000 """ import logging import sys import os from fastapi.responses import HTMLResponse # Ensure project root is on path for imports sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # Disable OpenEnv's broken web interface â we serve our own at / os.environ["ENABLE_WEB_INTERFACE"] = "false" from openenv.core.env_server.http_server import create_app from models import GlucoAction, GlucoObservation from server.glucorl_environment import GlucoRLEnvironment from server.graders import grade, grade_detailed logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Create the OpenEnv-compliant FastAPI app # --------------------------------------------------------------------------- app = create_app( GlucoRLEnvironment, GlucoAction, GlucoObservation, env_name="oasis", max_concurrent_envs=1, ) # --------------------------------------------------------------------------- # Custom endpoints # --------------------------------------------------------------------------- @app.get("/tasks", tags=["Environment Info"]) async def list_tasks(): """Return descriptions of all 4 OASIS tasks.""" return [ { "id": 1, "name": "Basal Rate Control", "difficulty": "easy", "description": ( "Single stable adult patient, no meals. " "Optimise basal insulin rate to keep glucose in " "70-180 mg/dL for a full 24-hour simulated day." ), }, { "id": 2, "name": "Meal Bolus Timing", "difficulty": "medium", "description": ( "Same adult patient with 3 announced daily meals " "(breakfast 50g, lunch 70g, dinner 80g). " "Deliver correct bolus doses at the right time to " "prevent post-meal spikes while avoiding hypoglycemia." ), }, { "id": 3, "name": "Cross-Patient Generalisation", "difficulty": "hard", "description": ( "Random patient sampled from 30 profiles " "(adolescent, adult, child). Meals are NOT announced. " "Develop a policy that generalises across varied " "patient physiology without knowing which patient " "is being treated." ), }, { "id": 4, "name": "Sick Day Management", "difficulty": "expert", "description": ( "Random patient with simulated illness causing 1.5-2.5x " "insulin resistance starting at an unknown time. " "Meals and exercise are unannounced. The agent must " "detect rising glucose from resistance and adapt its " "dosing strategy without being told illness is occurring." ), }, ] @app.get("/healthz", tags=["Health"]) async def healthz(): """Quick health check â verifies the environment can be instantiated.""" try: env = GlucoRLEnvironment() return {"status": "ok"} except Exception as e: logger.error("Health check failed: %s", e, exc_info=True) return {"status": "error", "error": str(e)} @app.post("/grade", tags=["Evaluation"]) async def grade_episode(task_id: int = 1): """ Grade the current completed episode and return detailed score breakdown. Instantiates an environment to access state. Note: for stateful grading, use the WebSocket client to run an episode, then call state() and grade client-side. This endpoint is provided for convenience and testing. Returns 400 if no episode data is available. """ try: env = GlucoRLEnvironment() state = env.state if not state.glucose_history: return {"error": "No episode data available. Run an episode first via WebSocket."} result = grade_detailed(task_id, state) return result except ValueError as e: return {"error": str(e)} except Exception as e: logger.error("Grade endpoint failed: %s", e, exc_info=True) return {"error": str(e)} # --------------------------------------------------------------------------- # OASIS Web Interface â served at / for HuggingFace Spaces # --------------------------------------------------------------------------- APP_HTML = """