Spaces:
Sleeping
Sleeping
| """ | |
| app.py | |
| ββββββ | |
| FastAPI application serving CogTraceEnv as an OpenEnv HTTP API. | |
| Endpoints: | |
| POST /reset β Observation | |
| POST /step β {observation, reward, done, info} | |
| GET /state β EnvState | |
| GET /tasks β list of available tasks | |
| GET /health β {"status": "ok"} | |
| GET /openenv.yaml β serve the spec file | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import yaml | |
| from typing import Any, Dict, Optional | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel | |
| from env.cognitive_env import CogTraceEnv | |
| from env.patient_simulator import PatientConfig | |
| from env.models import Action | |
| app = FastAPI( | |
| title="CogTraceEnv", | |
| description="OpenEnv environment for Alzheimer's cognitive monitoring", | |
| version="1.0.0", | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def serve_demo(): | |
| demo_path = os.path.join(os.path.dirname(__file__), "demo.html") | |
| if not os.path.exists(demo_path): | |
| raise HTTPException(status_code=404, detail="demo.html not found") | |
| with open(demo_path) as f: | |
| return f.read() | |
| # Global env instance (single-session server) | |
| _env: Optional[CogTraceEnv] = None | |
| # βββ Request/Response models ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ResetRequest(BaseModel): | |
| true_stage: Optional[int] = None # 0β4; None = random | |
| episode_length: int = 30 | |
| decline_rate: float = 0.01 | |
| noise_level: float = 1.0 | |
| seed: Optional[int] = None | |
| patient_id: str = "patient_001" | |
| anomaly_day: Optional[int] = None | |
| anomaly_duration: int = 5 | |
| class StepRequest(BaseModel): | |
| action: int # 0β3 | |
| # βββ Routes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def health(): | |
| return {"status": "ok", "env": "CogTraceEnv-v1"} | |
| def reset(req: ResetRequest = ResetRequest()): | |
| global _env | |
| import random | |
| stage = req.true_stage if req.true_stage is not None else random.randint(1, 3) | |
| cfg = PatientConfig( | |
| true_stage=stage, | |
| episode_length=req.episode_length, | |
| decline_rate=req.decline_rate, | |
| noise_level=req.noise_level, | |
| seed=req.seed, | |
| patient_id=req.patient_id, | |
| anomaly_day=req.anomaly_day, | |
| anomaly_duration=req.anomaly_duration, | |
| ) | |
| _env = CogTraceEnv(config=cfg) | |
| obs = _env.reset() | |
| return obs.model_dump() | |
| def step(req: StepRequest): | |
| if _env is None: | |
| raise HTTPException(status_code=400, detail="Call /reset first.") | |
| try: | |
| action = Action(action=req.action) | |
| obs, reward, done, info = _env.step(action) | |
| return { | |
| "observation": obs.model_dump(), | |
| "reward": reward.model_dump(), | |
| "done": done, | |
| "info": info.model_dump(), | |
| } | |
| except RuntimeError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| def state(): | |
| if _env is None: | |
| raise HTTPException(status_code=400, detail="Call /reset first.") | |
| return _env.state().model_dump() | |
| def list_tasks(): | |
| return { | |
| "tasks": [ | |
| { | |
| "id": "task1_easy", | |
| "name": "Cognitive Stage Classification", | |
| "difficulty": "easy", | |
| "description": ( | |
| "Given one snapshot of behavioral metrics, " | |
| "predict the patient's Alzheimer's stage (0β4)." | |
| ), | |
| }, | |
| { | |
| "id": "task2_medium", | |
| "name": "Anomaly Timing Detection", | |
| "difficulty": "medium", | |
| "description": ( | |
| "Observe 7 days of signals. Raise an alert on " | |
| "the day you detect an anomaly." | |
| ), | |
| }, | |
| { | |
| "id": "task3_hard", | |
| "name": "Full Triage Episode", | |
| "difficulty": "hard", | |
| "description": ( | |
| "Manage a 30-step episode, balancing sensitivity " | |
| "and specificity across declining patient trajectories." | |
| ), | |
| }, | |
| ] | |
| } | |
| def serve_yaml(): | |
| yaml_path = os.path.join(os.path.dirname(__file__), "openenv.yaml") | |
| if not os.path.exists(yaml_path): | |
| raise HTTPException(status_code=404, detail="openenv.yaml not found") | |
| with open(yaml_path) as f: | |
| return f.read() | |
| # ββ Entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=False) | |