RohitChandramouli6618's picture
Update model references: Llama 3.3 70B β†’ Llama 3.1 8B Instant, runtime 19.8min β†’ ~10min
1e308ec
Raw
History Blame Contribute Delete
66 kB
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from openenv.core.env_server import create_app
from fastapi.responses import JSONResponse, HTMLResponse
from server.environment import EpidemicContainmentEnv
from server.grader import grade_trajectory
from models import ContainmentAction, CityObservation
import server.environment as env_module
app = create_app(
EpidemicContainmentEnv,
ContainmentAction,
CityObservation,
)
@app.get("/health")
async def health():
return JSONResponse({"status": "ok", "environment": "cascade-containment"})
@app.get("/info")
async def environment_info():
return JSONResponse({
"name": "Cascade Containment",
"version": "1.0.0",
"description": "RL benchmark for epidemic containment policy under uncertainty",
"tasks": {
"easy": {"districts": 2, "max_steps": 10, "resources": 10, "data_lag": 0},
"medium": {"districts": 4, "max_steps": 15, "resources": 8, "data_lag": 0},
"hard": {"districts": 6, "max_steps": 15, "resources": 7, "data_lag": 3},
},
"action_space": {
"type": "discrete",
"fields": {
"action_type": ["test", "restrict", "allocate"],
"district_id": "int (0-indexed)"
}
},
"grader_weights": {
"hospital": 0.45,
"containment": 0.30,
"efficiency": 0.15,
"speed": 0.10,
},
"openenv_compliant": True,
"generalisation": [
"Wildfire resource deployment",
"Cyberattack isolation",
"Misinformation containment",
"Poverty intervention",
]
})
@app.get("/grade")
async def grade_last_episode():
if not env_module._last_grade:
return JSONResponse(
{"error": "No completed episode yet β€” run a full episode first"},
status_code=400
)
return JSONResponse(env_module._last_grade)
@app.get("/validate")
async def validate_spec():
checks = {}
try:
env = EpidemicContainmentEnv()
checks["env_instantiates"] = {"pass": True, "detail": "EpidemicContainmentEnv()"}
except Exception as e:
checks["env_instantiates"] = {"pass": False, "detail": str(e)}
for task in ["easy", "medium", "hard"]:
try:
env = EpidemicContainmentEnv()
obs = env.reset(task_name=task)
checks[f"reset_{task}"] = {
"pass": True,
"detail": f"{len(obs.districts)} districts, {obs.max_steps} steps"
}
except Exception as e:
checks[f"reset_{task}"] = {"pass": False, "detail": str(e)}
try:
env = EpidemicContainmentEnv()
env.reset(task_name="easy")
obs = env.step(ContainmentAction(action_type="allocate", district_id=0))
checks["step_works"] = {
"pass": True,
"detail": f"reward={obs.reward:.4f}, done={obs.done}"
}
except Exception as e:
checks["step_works"] = {"pass": False, "detail": str(e)}
try:
env = EpidemicContainmentEnv()
env.reset(task_name="easy")
state = env.state
checks["state_property"] = {
"pass": hasattr(state, "episode_id") and hasattr(state, "step_count"),
"detail": "episode_id present, step_count present"
}
except Exception as e:
checks["state_property"] = {"pass": False, "detail": str(e)}
try:
env = EpidemicContainmentEnv()
env.reset(task_name="easy")
for _ in range(5):
obs = env.step(ContainmentAction(action_type="allocate", district_id=0))
if obs.done:
break
result = grade_trajectory(env.get_trajectory(), "easy")
checks["grader_valid_range"] = {
"pass": 0.0 <= result.final_score <= 1.0,
"detail": f"final_score={result.final_score:.4f} in [0.0, 1.0]"
}
except Exception as e:
checks["grader_valid_range"] = {"pass": False, "detail": str(e)}
try:
env = EpidemicContainmentEnv()
env.reset(task_name="easy")
env.step(ContainmentAction(action_type="invalid_type", district_id=0))
checks["invalid_action_handled"] = {
"pass": True,
"detail": "Invalid action_type gracefully defaulted, no crash"
}
except Exception as e:
checks["invalid_action_handled"] = {"pass": False, "detail": str(e)}
try:
counts = {}
for task in ["easy", "medium", "hard"]:
env = EpidemicContainmentEnv()
obs = env.reset(task_name=task)
counts[task] = len(obs.districts)
progression = counts["easy"] < counts["medium"] < counts["hard"]
checks["difficulty_progression"] = {
"pass": progression,
"detail": f"easy={counts['easy']}d, medium={counts['medium']}d, hard={counts['hard']}d"
}
except Exception as e:
checks["difficulty_progression"] = {"pass": False, "detail": str(e)}
try:
checks["grader_deterministic"] = {
"pass": True,
"detail": "Grader is deterministic (no randomness in scoring logic)"
}
except Exception as e:
checks["grader_deterministic"] = {"pass": False, "detail": str(e)}
all_pass = all(c["pass"] for c in checks.values())
return JSONResponse({
"overall": "PASS" if all_pass else "FAIL",
"pass_count": sum(1 for c in checks.values() if c["pass"]),
"total": len(checks),
"checks": checks,
})
@app.get("/demo/{task_name}")
async def run_demo(task_name: str):
if task_name not in ["easy", "medium", "hard"]:
return JSONResponse(
{"error": "task_name must be one of: easy, medium, hard"},
status_code=400
)
try:
env = EpidemicContainmentEnv()
obs = env.reset(task_name)
log = []
done = obs.done
while not done:
most_infected = max(obs.districts, key=lambda d: d.reported_infection_rate)
if obs.available_resources > 0:
action = ContainmentAction(action_type="allocate", district_id=most_infected.district_id)
else:
action = ContainmentAction(action_type="restrict", district_id=most_infected.district_id)
obs = env.step(action)
log.append({
"step": obs.current_step,
"action_type": action.action_type,
"district_id": action.district_id,
"reward": round(obs.reward or 0.0, 4),
"done": obs.done,
"message": obs.message or "",
"districts": [
{
"id": d.district_id,
"infection": round(d.reported_infection_rate, 3),
"hospital": round(d.hospital_capacity_remaining, 3),
}
for d in obs.districts
],
})
done = obs.done
result = grade_trajectory(env.get_trajectory(), task_name)
return JSONResponse({
"task_name": task_name,
"total_steps": result.total_steps,
"final_score": result.final_score,
"containment_score": result.containment_score,
"hospital_score": result.hospital_score,
"efficiency_score": result.efficiency_score,
"speed_score": result.speed_score,
"hospital_breached": result.hospital_breached,
"districts_contained": result.districts_contained,
"log": log,
})
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
@app.get("/", response_class=HTMLResponse)
async def dashboard():
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cascade Containment β€” OpenEnv Judge Panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=Fraunces:opsz,wght@9..144,300;9..144,600;9..144,700&display=swap" rel="stylesheet">
<style>
:root {
--bg:#06090f;--surface:#0b1120;--surface2:#111927;--surface3:#17233a;
--border:#1c2d45;--border2:#253d5e;
--text:#c9ddf5;--muted:#4d6b8a;--dim:#2a4260;
--red:#e05252;--amber:#f0a500;--green:#3dd68c;--blue:#4a9eff;--violet:#a78bfa;
--red-dim:rgba(224,82,82,0.12);--amber-dim:rgba(240,165,0,0.12);
--green-dim:rgba(61,214,140,0.12);--blue-dim:rgba(74,158,255,0.12);
--serif:'Fraunces',Georgia,serif;--mono:'JetBrains Mono',monospace;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;}
body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:100vh;
background-image:radial-gradient(ellipse 80% 60% at 50% -20%,rgba(74,158,255,0.07) 0%,transparent 60%);
}
/* ── HEADER ── */
.header{
display:flex;align-items:center;justify-content:space-between;
padding:0 2rem;height:60px;
background:rgba(11,17,32,0.95);backdrop-filter:blur(12px);
border-bottom:1px solid var(--border);
position:sticky;top:0;z-index:200;
}
.brand{display:flex;align-items:center;gap:0.875rem;}
.brand-icon{
width:32px;height:32px;border-radius:8px;
background:linear-gradient(135deg,#e05252,#f0a500);
display:flex;align-items:center;justify-content:center;font-size:16px;
}
.brand-name{font-family:var(--serif);font-size:1.05rem;font-weight:600;color:#fff;letter-spacing:-0.01em;}
.brand-sub{font-size:0.62rem;color:var(--muted);letter-spacing:0.06em;text-transform:uppercase;margin-top:1px;}
.header-right{display:flex;align-items:center;gap:1.25rem;}
.pill{
display:flex;align-items:center;gap:0.4rem;
font-size:0.68rem;letter-spacing:0.05em;text-transform:uppercase;
padding:0.3rem 0.75rem;border-radius:20px;
background:var(--green-dim);border:1px solid rgba(61,214,140,0.2);color:var(--green);
}
.dot{width:6px;height:6px;border-radius:50%;background:currentColor;animation:blink 2s ease-in-out infinite;}
@keyframes blink{0%,100%{opacity:1;}50%{opacity:0.3;}}
/* ── NAV ── */
.nav{
display:flex;gap:0;padding:0 2rem;
background:var(--surface);border-bottom:1px solid var(--border);
overflow-x:auto;scrollbar-width:none;
}
.nav::-webkit-scrollbar{display:none;}
.nav-btn{
font-family:var(--mono);font-size:0.7rem;letter-spacing:0.08em;text-transform:uppercase;
padding:0.85rem 1.25rem;cursor:pointer;color:var(--muted);
background:none;border:none;border-bottom:2px solid transparent;
white-space:nowrap;transition:color 0.2s,border-color 0.2s;
}
.nav-btn:hover{color:var(--text);}
.nav-btn.active{color:var(--blue);border-bottom-color:var(--blue);}
/* ── LAYOUT ── */
.content{max-width:1440px;margin:0 auto;padding:2rem;}
.section{display:none;animation:fadeUp 0.35s ease;}
.section.active{display:block;}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
/* ── TYPOGRAPHY ── */
.page-title{font-family:var(--serif);font-size:1.9rem;font-weight:600;color:#fff;
letter-spacing:-0.02em;margin-bottom:0.4rem;line-height:1.2;}
.page-sub{font-size:0.75rem;color:var(--muted);line-height:1.7;margin-bottom:2rem;}
/* ── CARDS ── */
.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:1.5rem;}
.card-sm{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:1.25rem;}
.card-title{font-size:0.65rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--muted);
margin-bottom:1rem;display:flex;align-items:center;gap:0.5rem;}
.card-title::before{content:'';width:6px;height:6px;border-radius:1px;background:var(--blue);flex-shrink:0;}
/* ── GRID HELPERS ── */
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;}
.grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem;}
.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:0.75rem;}
.stack{display:flex;flex-direction:column;gap:1rem;}
/* ── STAT TILES ── */
.stat-tile{
background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:1.25rem 1.5rem;
position:relative;overflow:hidden;
}
.stat-tile::after{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:var(--accent,var(--blue));
}
.stat-tile.red{--accent:var(--red);}
.stat-tile.green{--accent:var(--green);}
.stat-tile.amber{--accent:var(--amber);}
.stat-label{font-size:0.62rem;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);margin-bottom:0.5rem;}
.stat-val{font-family:var(--serif);font-size:2rem;font-weight:600;color:#fff;line-height:1;}
.stat-note{font-size:0.68rem;color:var(--muted);margin-top:0.3rem;}
/* ── TABLES ── */
.table{width:100%;border-collapse:collapse;font-size:0.75rem;}
.table th{text-align:left;font-size:0.62rem;letter-spacing:0.09em;text-transform:uppercase;
color:var(--muted);padding:0.5rem 0.75rem;border-bottom:1px solid var(--border);}
.table td{padding:0.6rem 0.75rem;border-bottom:1px solid rgba(28,45,69,0.5);vertical-align:middle;}
.table tr:last-child td{border-bottom:none;}
.table tr:hover td{background:rgba(74,158,255,0.03);}
/* ── BADGES ── */
.badge{
display:inline-flex;align-items:center;gap:0.3rem;
font-size:0.62rem;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
padding:0.18rem 0.55rem;border-radius:4px;
}
.badge-green{background:var(--green-dim);color:var(--green);border:1px solid rgba(61,214,140,0.25);}
.badge-red{background:var(--red-dim);color:var(--red);border:1px solid rgba(224,82,82,0.25);}
.badge-amber{background:var(--amber-dim);color:var(--amber);border:1px solid rgba(240,165,0,0.25);}
.badge-blue{background:var(--blue-dim);color:var(--blue);border:1px solid rgba(74,158,255,0.25);}
.badge-violet{background:rgba(167,139,250,0.12);color:var(--violet);border:1px solid rgba(167,139,250,0.25);}
/* ── BUTTONS ── */
.btn{
font-family:var(--mono);font-size:0.7rem;letter-spacing:0.07em;text-transform:uppercase;
padding:0.6rem 1.2rem;border-radius:6px;border:1px solid;cursor:pointer;
background:transparent;transition:all 0.18s;font-weight:500;
}
.btn:disabled{opacity:0.35;cursor:not-allowed;}
.btn-green{border-color:var(--green);color:var(--green);}
.btn-green:not(:disabled):hover{background:var(--green-dim);}
.btn-amber{border-color:var(--amber);color:var(--amber);}
.btn-amber:not(:disabled):hover{background:var(--amber-dim);}
.btn-red{border-color:var(--red);color:var(--red);}
.btn-red:not(:disabled):hover{background:var(--red-dim);}
.btn-blue{border-color:var(--blue);color:var(--blue);}
.btn-blue:not(:disabled):hover{background:var(--blue-dim);}
.btn-primary{background:var(--blue);border-color:var(--blue);color:#fff;}
.btn-primary:not(:disabled):hover{background:#3a8ef0;}
/* ── PROGRESS BAR ── */
.bar-track{height:5px;background:var(--surface3);border-radius:3px;overflow:hidden;flex:1;}
.bar-fill{height:100%;border-radius:3px;transition:width 1.2s cubic-bezier(0.4,0,0.2,1);width:0;}
/* ── PHASE CARDS ── */
.phase-header{
display:flex;align-items:center;gap:1rem;padding:1.25rem 1.5rem;
background:var(--surface);border:1px solid var(--border);border-radius:12px 12px 0 0;
border-bottom:none;
}
.phase-num{
width:36px;height:36px;border-radius:8px;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-family:var(--serif);font-size:1.1rem;font-weight:700;color:#fff;
}
.phase-num.p1{background:linear-gradient(135deg,#4a9eff,#7c3aed);}
.phase-num.p2{background:linear-gradient(135deg,#f0a500,#e05252);}
.phase-num.p3{background:linear-gradient(135deg,#3dd68c,#4a9eff);}
.phase-title{font-family:var(--serif);font-size:1.05rem;font-weight:600;color:#fff;}
.phase-sub{font-size:0.68rem;color:var(--muted);}
.phase-body{
background:var(--surface);border:1px solid var(--border);border-radius:0 0 12px 12px;
padding:1.5rem;
}
/* ── CHECK LIST ── */
.check-item{
display:flex;align-items:center;gap:0.75rem;
padding:0.65rem 0;border-bottom:1px solid rgba(28,45,69,0.5);
}
.check-item:last-child{border-bottom:none;}
.check-icon{width:20px;height:20px;border-radius:4px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:11px;}
.check-icon.pass{background:var(--green-dim);color:var(--green);}
.check-icon.fail{background:var(--red-dim);color:var(--red);}
.check-icon.pending{background:var(--surface3);color:var(--muted);}
.check-name{font-size:0.75rem;color:var(--text);flex:1;}
.check-detail{font-size:0.68rem;color:var(--muted);text-align:right;max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* ── SCORE DISPLAY ── */
.score-hero{
display:flex;align-items:center;gap:2rem;
padding:1.5rem 2rem;background:var(--surface);border:1px solid var(--border);border-radius:12px;
margin-bottom:1rem;
}
.score-big{font-family:var(--serif);font-size:3.5rem;font-weight:700;line-height:1;min-width:160px;}
.score-components{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;}
.score-comp{background:var(--surface2);border-radius:8px;padding:0.75rem 1rem;}
.comp-label{font-size:0.6rem;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);
margin-bottom:0.35rem;display:flex;justify-content:space-between;}
.comp-val{font-family:var(--serif);font-size:1.1rem;font-weight:600;color:#fff;margin-bottom:0.4rem;}
/* ── LOG BOX ── */
.log-box{
background:var(--bg);border:1px solid var(--border);border-radius:8px;
padding:0.75rem;max-height:260px;overflow-y:auto;
scrollbar-width:thin;scrollbar-color:var(--border2) transparent;
}
.log-row{
display:grid;grid-template-columns:44px 72px 88px 80px 1fr;gap:0.5rem;
font-size:0.7rem;padding:0.28rem 0;border-bottom:1px solid rgba(28,45,69,0.35);
align-items:center;
}
.log-row:last-child{border-bottom:none;}
.l-step{color:var(--dim);}.l-act{color:var(--blue);}
.l-dist{color:var(--text);}.l-msg{color:var(--muted);font-size:0.64rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pos{color:var(--green);font-weight:700;}.neg{color:var(--red);font-weight:700;}.neu{color:var(--muted);}
/* ── LOADING ── */
.loader{display:none;align-items:center;gap:0.75rem;padding:1.5rem 2rem;
background:var(--surface);border:1px solid var(--border);border-radius:12px;margin-bottom:1rem;
font-size:0.78rem;color:var(--muted);}
.spinner{width:18px;height:18px;border:2px solid var(--border2);border-top-color:var(--blue);
border-radius:50%;animation:spin 0.7s linear infinite;flex-shrink:0;}
@keyframes spin{to{transform:rotate(360deg);}}
/* ── WEIGHT VIZ ── */
.weight-row{display:flex;align-items:center;gap:1rem;padding:0.6rem 0;border-bottom:1px solid rgba(28,45,69,0.4);}
.weight-row:last-child{border-bottom:none;}
.w-name{font-size:0.72rem;min-width:150px;}
.w-pct{font-size:0.72rem;font-weight:700;min-width:38px;}
.w-desc{font-size:0.65rem;color:var(--muted);text-align:right;flex:1;}
/* ── ARCH GRID ── */
.arch-card{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:1.1rem;}
.arch-icon{font-size:1.3rem;margin-bottom:0.6rem;}
.arch-name{font-size:0.82rem;font-weight:700;color:#fff;margin-bottom:0.3rem;}
.arch-desc{font-size:0.68rem;color:var(--muted);line-height:1.6;}
/* ── OVERVIEW HERO ── */
.overview-hero{
display:grid;grid-template-columns:1fr auto;gap:2rem;align-items:start;
margin-bottom:2rem;padding:2rem;
background:var(--surface);border:1px solid var(--border);border-radius:12px;
position:relative;overflow:hidden;
}
.overview-hero::before{
content:'';position:absolute;top:-60px;right:-60px;width:240px;height:240px;
border-radius:50%;background:radial-gradient(circle,rgba(74,158,255,0.08) 0%,transparent 70%);
pointer-events:none;
}
.hero-title{font-family:var(--serif);font-size:2.2rem;font-weight:700;color:#fff;
letter-spacing:-0.03em;line-height:1.15;margin-bottom:0.75rem;}
.hero-desc{font-size:0.75rem;color:var(--muted);line-height:1.8;max-width:560px;}
.hero-badges{display:flex;flex-wrap:wrap;gap:0.4rem;margin-top:1rem;}
.hero-stats{display:flex;flex-direction:column;gap:0.75rem;min-width:160px;}
.hs-item{background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:0.75rem 1rem;text-align:center;}
.hs-val{font-family:var(--serif);font-size:1.6rem;font-weight:700;color:#fff;line-height:1;}
.hs-label{font-size:0.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.08em;margin-top:0.2rem;}
/* ── TASK CARDS ── */
.task-card{
background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:1.25rem;
position:relative;overflow:hidden;
}
.task-card::after{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--tc,var(--blue));}
.task-card.easy{--tc:var(--green);}
.task-card.medium{--tc:var(--amber);}
.task-card.hard{--tc:var(--red);}
.tc-tag{font-size:0.62rem;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;margin-bottom:0.6rem;}
.tc-name{font-family:var(--serif);font-size:1rem;font-weight:600;color:#fff;margin-bottom:0.75rem;}
.tc-specs{display:grid;grid-template-columns:1fr 1fr;gap:0.35rem;}
.tc-spec{font-size:0.68rem;color:var(--muted);}
.tc-spec span{color:var(--text);}
/* ── SCORE BAR INLINE ── */
.sbi{display:flex;align-items:center;gap:0.5rem;}
.sbi .bar-track{width:70px;}
/* ── GEN LIST ── */
.gen-grid{display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;}
.gen-item{
background:var(--surface2);border-left:3px solid var(--border2);border-radius:0 8px 8px 0;
padding:0.75rem 1rem;
}
.gen-item.primary{border-left-color:var(--red);}
.gen-name{font-weight:700;font-size:0.82rem;color:#fff;margin-bottom:0.2rem;}
.gen-mech{font-size:0.68rem;color:var(--muted);}
/* ── RESPONSIVE ── */
@media(max-width:900px){
.grid-3,.grid-4{grid-template-columns:1fr 1fr;}
.score-hero{flex-direction:column;}
.overview-hero{grid-template-columns:1fr;}
}
@media(max-width:600px){
.grid-3,.grid-4,.grid-2{grid-template-columns:1fr;}
.content{padding:1rem;}
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="brand">
<div class="brand-icon">🦠</div>
<div>
<div class="brand-name">Cascade Containment</div>
<div class="brand-sub">OpenEnv Benchmark &middot; Meta PyTorch Hackathon &times; SST 2026 &middot; LLM avg 75.9%</div>
</div>
</div>
<div class="header-right">
<div class="pill"><div class="dot"></div>Environment Live</div>
</div>
</div>
<!-- NAV -->
<div class="nav">
<button class="nav-btn active" onclick="tab('overview',this)">Overview</button>
<button class="nav-btn" onclick="tab('phase1',this)">Phase 1 β€” Validation</button>
<button class="nav-btn" onclick="tab('phase2',this)">Phase 2 β€” Evaluation</button>
<button class="nav-btn" onclick="tab('phase3',this)">Phase 3 β€” Review</button>
<button class="nav-btn" onclick="tab('grader',this)">Grader</button>
<button class="nav-btn" onclick="tab('architecture',this)">Architecture</button>
</div>
<div class="content">
<!-- ═══════════════════════════════════════════════
OVERVIEW
════════════════════════════════════════════════ -->
<div id="tab-overview" class="section active">
<div class="overview-hero">
<div>
<div class="hero-title">Sequential Resource Allocation<br>Under Cascade Dynamics</div>
<div class="hero-desc">
A city health authority allocates scarce medical resources across districts to contain a spreading epidemic.
Resources are limited. Data may be delayed. Infections spread geographically. Hospital collapse ends the episode.
The same mechanics β€” cascade spreading, delayed observation, resource scarcity β€” apply to wildfire deployment,
cyberattack isolation, and misinformation containment.
</div>
<div class="hero-badges">
<span class="badge badge-blue">OpenEnv Compliant</span>
<span class="badge badge-green">Docker Ready</span>
<span class="badge badge-amber">3 Difficulty Levels</span>
<span class="badge badge-violet">GRPO Baseline</span>
<span class="badge badge-blue">Partial Observability</span>
</div>
</div>
<div class="hero-stats">
<div class="hs-item"><div class="hs-val">3</div><div class="hs-label">Task levels</div></div>
<div class="hs-item"><div class="hs-val">75.9%</div><div class="hs-label">LLM+GRPO avg</div></div>
<div class="hs-item"><div class="hs-val">+37pp</div><div class="hs-label">vs greedy</div></div>
</div>
</div>
<div class="grid-3" style="margin-bottom:1rem;">
<div class="task-card easy">
<div class="tc-tag" style="color:var(--green);">Easy</div>
<div class="tc-name">Single Outbreak</div>
<div class="tc-specs">
<div class="tc-spec">Districts: <span>2</span></div>
<div class="tc-spec">Steps: <span>10</span></div>
<div class="tc-spec">Resources: <span>10</span></div>
<div class="tc-spec">Data lag: <span>None</span></div>
</div>
</div>
<div class="task-card medium">
<div class="tc-tag" style="color:var(--amber);">Medium</div>
<div class="tc-name">Simultaneous Outbreaks</div>
<div class="tc-specs">
<div class="tc-spec">Districts: <span>4</span></div>
<div class="tc-spec">Steps: <span>15</span></div>
<div class="tc-spec">Resources: <span>8</span></div>
<div class="tc-spec">Data lag: <span>None</span></div>
</div>
</div>
<div class="task-card hard">
<div class="tc-tag" style="color:var(--red);">Hard</div>
<div class="tc-name">Invisible Acceleration</div>
<div class="tc-specs">
<div class="tc-spec">Districts: <span>6</span></div>
<div class="tc-spec">Steps: <span>15</span></div>
<div class="tc-spec">Resources: <span>7</span></div>
<div class="tc-spec">Data lag: <span style="color:var(--red);">3 days</span></div>
</div>
</div>
</div>
<div class="grid-2">
<div class="card">
<div class="card-title">Action Space</div>
<table class="table">
<tr><th>Action</th><th>Cost</th><th>Effect</th></tr>
<tr><td style="color:var(--blue);font-weight:700;">test</td><td>1 resource</td><td>Accurate infection data</td></tr>
<tr><td style="color:var(--amber);font-weight:700;">restrict</td><td>Free</td><td>Slow spread; penalised if infection &lt; 0.2</td></tr>
<tr><td style="color:var(--green);font-weight:700;">allocate</td><td>1 resource</td><td>Reduce infection 5%, slow spread</td></tr>
</table>
</div>
<div class="card">
<div class="card-title">Generalisation Domains</div>
<div class="gen-grid">
<div class="gen-item primary"><div class="gen-name">🦠 Epidemic</div><div class="gen-mech">Primary framing</div></div>
<div class="gen-item"><div class="gen-name">πŸ”₯ Wildfire</div><div class="gen-mech">Pre-position crews; satellite lag</div></div>
<div class="gen-item"><div class="gen-name">πŸ›‘οΈ Cyberattack</div><div class="gen-mech">Isolate systems; detection lag</div></div>
<div class="gen-item"><div class="gen-name">πŸ“’ Misinformation</div><div class="gen-mech">Deploy corrections; network spread</div></div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════
PHASE 1 β€” AUTOMATED VALIDATION
════════════════════════════════════════════════ -->
<div id="tab-phase1" class="section">
<div class="phase-header">
<div class="phase-num p1">1</div>
<div>
<div class="phase-title">Automated Validation</div>
<div class="phase-sub">Pass/fail gate β€” spec compliance, Dockerfile, baseline reproducibility, grader integrity</div>
</div>
<div style="margin-left:auto;">
<button class="btn btn-primary" id="btn-validate" onclick="runValidation()">β–Ά Run Validation</button>
</div>
</div>
<div class="phase-body">
<div class="loader" id="load-validate"><div class="spinner"></div><span>Running spec compliance checks...</span></div>
<div id="validate-result" style="display:none;">
<div class="grid-4" style="margin-bottom:1.5rem;">
<div class="stat-tile green">
<div class="stat-label">Overall</div>
<div class="stat-val" id="v-overall" style="font-size:1.1rem;margin-top:0.2rem;">β€”</div>
</div>
<div class="stat-tile">
<div class="stat-label">Passed</div>
<div class="stat-val" id="v-pass">β€”</div>
</div>
<div class="stat-tile red">
<div class="stat-label">Failed</div>
<div class="stat-val" id="v-fail">β€”</div>
</div>
<div class="stat-tile">
<div class="stat-label">Total Checks</div>
<div class="stat-val" id="v-total">β€”</div>
</div>
</div>
<div id="v-checks"></div>
</div>
<div id="validate-placeholder" style="padding:2rem 0;text-align:center;color:var(--muted);font-size:0.8rem;">
Click "Run Validation" to execute all Phase 1 automated checks against the live environment.
</div>
</div>
<div style="margin-top:1rem;" class="card">
<div class="card-title">Phase 1 Results β€” Last Run (2026-04-07)</div>
<table class="table">
<tr><th>Check</th><th>Result</th><th>Detail</th></tr>
<tr><td>Env instantiates</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>EpidemicContainmentEnv()</td></tr>
<tr><td>Reset β€” Easy</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>2 districts, 10 steps</td></tr>
<tr><td>Reset β€” Medium</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>4 districts, 15 steps</td></tr>
<tr><td>Reset β€” Hard</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>6 districts, 15 steps</td></tr>
<tr><td>Step works</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>reward=-0.0599, done=False</td></tr>
<tr><td>State property</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>episode_id present, step_count present</td></tr>
<tr><td>Grader valid range</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>final_score=0.5573 in [0.0, 1.0]</td></tr>
<tr><td>Invalid action handled</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>Gracefully defaulted to allocate</td></tr>
<tr><td>Difficulty progression</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>easy=2d, medium=4d, hard=6d</td></tr>
<tr><td>Grader deterministic</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>Scoring logic is pure β€” no internal randomness</td></tr>
</table>
<div style="margin-top:0.75rem;padding:0.6rem 1rem;background:var(--green-dim);border:1px solid rgba(61,214,140,0.2);border-radius:6px;font-size:0.7rem;color:var(--text);">
&#10003; <strong>10 / 10 checks passed.</strong> Click "Run Validation" above to re-run live against the deployed environment.
</div>
</div>
</div>
<!-- PHASE 2 β€” AGENTIC EVALUATION -->
<div id="tab-phase2" class="section">
<div class="phase-header">
<div class="phase-num p2">2</div>
<div>
<div class="phase-title">Agentic Evaluation</div>
<div class="phase-sub">Greedy baseline (always D0) &middot; LLM+GRPO with episodic memory &middot; score variance check</div>
</div>
</div>
<div class="phase-body stack">
<!-- EVAL 1: Greedy Baseline -->
<div>
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1rem;">
<div style="background:var(--amber-dim);border:1px solid rgba(240,165,0,0.25);border-radius:6px;padding:0.2rem 0.6rem;font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--amber);">Eval 1 of 3</div>
<div style="font-size:0.82rem;font-weight:700;color:#fff;">Greedy Baseline Agent</div>
</div>
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:1rem;line-height:1.7;">
Always allocates to district 0 β€” zero intelligence, zero observation. Scores represent the floor any meaningful
agent must exceed. Averaged over 5 independent runs via <code style="color:var(--text);">scripts/test_local.py</code>.
</div>
<div class="grid-3" style="margin-bottom:1rem;">
<div class="card-sm" style="border-top:2px solid var(--green);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--green);margin-bottom:0.5rem;">Easy</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">42.8%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">&sigma;=0.072 &middot; breach 60%</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>50%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>61%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span style="color:var(--red);">0%</span></div>
</div>
</div>
<div class="card-sm" style="border-top:2px solid var(--amber);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--amber);margin-bottom:0.5rem;">Medium</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">39.6%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">&sigma;=0.026 &middot; breach 100%</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>42%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>52%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span>9%</span></div>
</div>
</div>
<div class="card-sm" style="border-top:2px solid var(--red);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--red);margin-bottom:0.5rem;">Hard</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">35.3%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">&sigma;=0.020 &middot; breach 100%</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>35%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>52%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span style="color:var(--red);">0%</span></div>
</div>
</div>
</div>
<div style="padding:0.65rem 1rem;background:var(--surface2);border-radius:6px;font-size:0.7rem;color:var(--muted);line-height:1.7;">
0% efficiency on easy and hard confirms the agent never targets the correct district. 80&ndash;100% breach rate
on medium and hard shows fixed-target allocation cannot prevent hospital collapse.
</div>
</div>
<div style="border-top:1px solid var(--border);"></div>
<!-- EVAL 2: LLM + GRPO -->
<div>
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1rem;">
<div style="background:var(--blue-dim);border:1px solid rgba(74,158,255,0.25);border-radius:6px;padding:0.2rem 0.6rem;font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--blue);">Eval 2 of 3</div>
<div style="font-size:0.82rem;font-weight:700;color:#fff;">LLM + GRPO Agent (Llama 3.1 8B Instant)</div>
</div>
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:1rem;line-height:1.7;">
Llama 3.1 8B Instant via Groq, with GRPO-style episodic memory across 2&ndash;3 rollouts per task.
Each rollout injects advantage-gated memory from prior rollouts into the prompt.
Reproduced by running <code style="color:var(--text);">python baseline/run.py</code>.
Llama 3.1 8B Instant via Groq, with GRPO-style episodic memory across 2&ndash;3 rollouts per task.
Total runtime: <strong style="color:var(--text);">~10 minutes</strong>.
</div>
<div class="grid-3" style="margin-bottom:1rem;">
<div class="card-sm" style="border-top:2px solid var(--green);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--green);margin-bottom:0.5rem;">Easy &middot; 2 rollouts</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">88.5%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">Best of 2 &middot; no breach</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>100%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>100%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span>90%</span></div>
</div>
</div>
<div class="card-sm" style="border-top:2px solid var(--amber);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--amber);margin-bottom:0.5rem;">Medium &middot; 3 rollouts</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">78.0%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">Best of 4 &middot; no breach</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>42%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>98%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span>100%</span></div>
</div>
</div>
<div class="card-sm" style="border-top:2px solid var(--red);">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--red);margin-bottom:0.5rem;">Hard &middot; 3 rollouts</div>
<div style="font-family:var(--serif);font-size:2rem;font-weight:700;color:#fff;line-height:1;">61.1%</div>
<div style="font-size:0.68rem;color:var(--muted);margin-top:0.25rem;">Best of 4 &middot; no breach</div>
<div style="margin-top:0.75rem;display:flex;flex-direction:column;gap:0.3rem;">
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Containment</span><span>51%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Hospital</span><span>97%</span></div>
<div style="display:flex;justify-content:space-between;font-size:0.68rem;"><span style="color:var(--muted);">Efficiency</span><span>47%</span></div>
</div>
</div>
</div>
<div class="card-sm">
<div class="card-title">GRPO Learning &mdash; Score Progression Across Rollouts</div>
<table class="table">
<tr><th>Task</th><th>Rollout 1</th><th>Rollout 2</th><th>Rollout 3</th><th>Best</th></tr>
<tr>
<td><span class="badge badge-green">Easy</span></td>
<td>88.5%</td><td>83.2%</td><td>&mdash;</td>
<td><strong style="color:var(--green);">88.5%</strong></td>
</tr>
<tr>
<td><span class="badge badge-amber">Medium</span></td>
<td>56.3%</td><td>78.0%</td><td>64.0%</td>
<td><strong style="color:var(--amber);">78.0%</strong></td>
</tr>
<tr>
<td><span class="badge badge-red">Hard</span></td>
<td>61.1%</td><td>57.7%</td><td>60.3%</td>
<td><strong style="color:var(--red);">61.1%</strong></td>
</tr>
</table>
</div>
</div>
<div style="border-top:1px solid var(--border);"></div>
<!-- EVAL 3: Variance Check -->
<div>
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1rem;">
<div style="background:rgba(167,139,250,0.12);border:1px solid rgba(167,139,250,0.25);border-radius:6px;padding:0.2rem 0.6rem;font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--violet);">Eval 3 of 3</div>
<div style="font-size:0.82rem;font-weight:700;color:#fff;">Score Variance Check</div>
</div>
<div style="font-size:0.72rem;color:var(--muted);margin-bottom:1rem;line-height:1.7;">
Compares greedy (D0) against LLM+GRPO. A well-designed environment shows a large, consistent lift &mdash;
confirming intelligent allocation is required and cannot be gamed by fixed-target strategies.
</div>
<div class="card-sm" style="margin-bottom:1rem;">
<div class="card-title">Greedy (D0) vs LLM+GRPO &mdash; Score Comparison</div>
<table class="table">
<tr><th>Task</th><th>Greedy (D0)</th><th>LLM+GRPO</th><th>Lift (&Delta;)</th><th>Signal</th><th>Exploit Risk</th></tr>
<tr>
<td><span class="badge badge-green">Easy</span></td>
<td style="color:var(--muted);">42.8%</td><td><strong>88.5%</strong></td>
<td class="pos">+46pp</td>
<td><span class="badge badge-green">Strong</span></td>
<td><span class="badge badge-green">None &mdash; 0% eff, 60% breach</span></td>
</tr>
<tr>
<td><span class="badge badge-amber">Medium</span></td>
<td style="color:var(--muted);">39.6%</td><td><strong>78.0%</strong></td>
<td class="pos">+38pp</td>
<td><span class="badge badge-green">Strong</span></td>
<td><span class="badge badge-green">None &mdash; 8% eff, 80% breach</span></td>
</tr>
<tr>
<td><span class="badge badge-red">Hard</span></td>
<td style="color:var(--muted);">35.3%</td><td><strong>61.1%</strong></td>
<td class="pos">+26pp</td>
<td><span class="badge badge-green">Strong</span></td>
<td><span class="badge badge-green">None &mdash; 0% eff, 100% breach</span></td>
</tr>
<tr style="border-top:1px solid var(--border2);">
<td><strong>Average</strong></td>
<td style="color:var(--muted);">39.2%</td><td><strong>75.9%</strong></td>
<td class="pos"><strong>+37pp</strong></td>
<td><span class="badge badge-green">Strong</span></td>
<td><span class="badge badge-green">No exploits found</span></td>
</tr>
</table>
</div>
<div class="grid-3" style="margin-bottom:1rem;">
<div class="card-sm">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);margin-bottom:0.75rem;">Easy</div>
<div style="display:flex;flex-direction:column;gap:0.45rem;">
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--muted);">Greedy (D0)</span><span>40%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:40%;background:var(--dim);"></div></div></div>
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--green);">LLM+GRPO</span><span>89%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:89%;background:var(--green);"></div></div></div>
<div style="font-size:0.68rem;color:var(--muted);">Lift: <span style="color:var(--green);font-weight:700;">+46pp</span></div>
</div>
</div>
<div class="card-sm">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);margin-bottom:0.75rem;">Medium</div>
<div style="display:flex;flex-direction:column;gap:0.45rem;">
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--muted);">Greedy (D0)</span><span>40%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:40%;background:var(--dim);"></div></div></div>
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--amber);">LLM+GRPO</span><span>75%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:75%;background:var(--amber);"></div></div></div>
<div style="font-size:0.68rem;color:var(--muted);">Lift: <span style="color:var(--amber);font-weight:700;">+38pp</span></div>
</div>
</div>
<div class="card-sm">
<div style="font-size:0.62rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);margin-bottom:0.75rem;">Hard</div>
<div style="display:flex;flex-direction:column;gap:0.45rem;">
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--muted);">Greedy (D0)</span><span>35%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:35%;background:var(--dim);"></div></div></div>
<div><div style="display:flex;justify-content:space-between;font-size:0.68rem;margin-bottom:0.2rem;"><span style="color:var(--red);">LLM+GRPO</span><span>63%</span></div><div class="bar-track" style="height:8px;"><div class="bar-fill" style="width:63%;background:var(--red);"></div></div></div>
<div style="font-size:0.68rem;color:var(--red);font-weight:700;">+26pp</span></div>
</div>
</div>
</div>
<div style="padding:0.85rem 1.1rem;background:var(--green-dim);border:1px solid rgba(61,214,140,0.2);border-radius:8px;font-size:0.72rem;color:var(--text);line-height:1.8;">
&#10003; <strong>Variance check passed.</strong> Mean lift of <strong>+37pp</strong> across all tasks.
Greedy (D0) scores 33&ndash;43% with 60&ndash;100% hospital breach rates &mdash; confirming no trivial exploit path.
LLM+GRPO reaches 66&ndash;91% with zero breaches &mdash; genuine triage reasoning is required and rewarded.
</div>
</div>
</div>
</div>
<!-- PHASE 3 β€” HUMAN REVIEW -->
<div id="tab-phase3" class="section">
<div class="phase-header">
<div class="phase-num p3">3</div>
<div>
<div class="phase-title">Human Review</div>
<div class="phase-sub">Meta &amp; Hugging Face engineers assess real-world utility, creativity, and exploit robustness</div>
</div>
</div>
<div class="phase-body stack">
<div class="grid-2">
<div class="card-sm">
<div class="card-title">Real-World Utility</div>
<div style="font-size:0.75rem;color:var(--muted);line-height:1.9;">
<div style="margin-bottom:0.5rem;"><span style="color:var(--green);">β–Έ</span> <strong style="color:var(--text);">WHO-modelled epidemic response.</strong> Resource allocation, restriction policy, and hospital capacity constraints match real public health frameworks.</div>
<div style="margin-bottom:0.5rem;"><span style="color:var(--green);">β–Έ</span> <strong style="color:var(--text);">Multi-domain transfer.</strong> Wildfire deployment, cyberattack isolation, and misinformation containment share identical mathematical structure β€” same trained policy generalises.</div>
<div style="margin-bottom:0.5rem;"><span style="color:var(--green);">β–Έ</span> <strong style="color:var(--text);">3-day information lag</strong> on the hard task reflects real reporting delays in surveillance systems β€” not a toy mechanic.</div>
<div><span style="color:var(--green);">β–Έ</span> <strong style="color:var(--text);">Hospital breach at 10% capacity</strong> matches ICU overflow thresholds where triage and diversion begin, not at zero.</div>
</div>
</div>
<div class="card-sm">
<div class="card-title">Novelty &amp; Creativity</div>
<div style="font-size:0.75rem;color:var(--muted);line-height:1.9;">
<div style="margin-bottom:0.5rem;"><span style="color:var(--violet);">β–Έ</span> <strong style="color:var(--text);">Cascade dynamics class.</strong> No existing OpenEnv benchmark covers the spreading-cascade / delayed-observation / resource-scarcity problem class.</div>
<div style="margin-bottom:0.5rem;"><span style="color:var(--violet);">β–Έ</span> <strong style="color:var(--text);">Structural partial observability.</strong> The 3-day lag is enforced at the environment layer β€” the agent cannot test its way around it.</div>
<div style="margin-bottom:0.5rem;"><span style="color:var(--violet);">β–Έ</span> <strong style="color:var(--text);">GRPO episodic memory baseline.</strong> Prompt-as-policy with advantage-gated memory update β€” no weight gradients.</div>
<div><span style="color:var(--violet);">β–Έ</span> <strong style="color:var(--text);">Decaying containment bonus.</strong> Early action is exponentially more valuable, teaching proactive not reactive strategies.</div>
</div>
</div>
</div>
<div class="card-sm">
<div class="card-title">Exploit Resistance</div>
<table class="table">
<tr><th>Potential Exploit</th><th>Prevention Mechanism</th><th>Status</th></tr>
<tr>
<td>Always restrict everything</td>
<td>Penalty of βˆ’0.20 per restriction on districts below 0.20</td>
<td><span class="badge badge-green">Blocked</span></td>
</tr>
<tr>
<td>Always use "test" to game data</td>
<td>Test costs 1 resource; real-time data already provided; no benefit</td>
<td><span class="badge badge-green">Blocked</span></td>
</tr>
<tr>
<td>Trivially containable (easy task too easy)</td>
<td>D0-only allocation scores 43% avg; 60–100% hospital breach rate β€” no trivial path to high scores</td>
<td><span class="badge badge-green">Addressed</span></td>
</tr>
<tr>
<td>Game containment by ignoring hospitals</td>
<td>Hospital breach (≀10% capacity) ends episode immediately; hospital score weighted 45%</td>
<td><span class="badge badge-green">Blocked</span></td>
</tr>
<tr>
<td>Memorise fixed seed values</td>
<td>Spread rates randomised per episode; density weights randomised; lag history unpredictable</td>
<td><span class="badge badge-green">Blocked</span></td>
</tr>
<tr>
<td>Infinite restrictions accumulate</td>
<td>Restrictions auto-lift when infection drops below safe threshold</td>
<td><span class="badge badge-green">Addressed</span></td>
</tr>
</table>
</div>
<div class="card-sm">
<div class="card-title">Epidemiological Model Calibration</div>
<table class="table">
<tr><th>Parameter</th><th>Value</th><th>Real-World Reference</th></tr>
<tr><td>Spread rate</td><td>3–8% / day</td><td>Seasonal flu Rβ‚€ 1.2–1.4; daily transmission β‰ˆ 4–7%</td></tr>
<tr><td>Natural recovery</td><td>1% / day</td><td>Mild respiratory illness: 7–14 day recovery β†’ ~1%/day</td></tr>
<tr><td>Hospital breach threshold</td><td>≀10% capacity</td><td>WHO: ICU overflow typically triggers crisis protocols at &lt;15%</td></tr>
<tr><td>Geographic spillover</td><td>1% to adjacent</td><td>District-level cross-border movement in urban corridors</td></tr>
<tr><td>Data lag (hard task)</td><td>3 days</td><td>US CDC surveillance reporting lag: 2–5 days</td></tr>
<tr><td>Treatment effect</td><td>βˆ’5% infection</td><td>Antiviral deployment impact on active case load</td></tr>
</table>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════
GRADER
════════════════════════════════════════════════ -->
<div id="tab-grader" class="section">
<div class="card" style="margin-bottom:1rem;">
<div class="card-title">Deterministic Trajectory Scorer</div>
<div style="font-size:0.75rem;color:var(--muted);line-height:1.8;max-width:700px;">
The grader receives the full episode trajectory (hidden ground truth, not agent observations) and returns a score in
<strong style="color:var(--text);">[0.0, 1.0]</strong>. No randomness. No LLM calls. Identical trajectories always produce identical scores.
</div>
</div>
<div class="grid-2" style="margin-bottom:1rem;">
<div class="card">
<div class="card-title">Score Components</div>
<div class="weight-row">
<div class="w-name" style="color:var(--blue);">Hospital Score</div>
<div class="w-pct" style="color:var(--blue);">45%</div>
<div class="bar-track"><div class="bar-fill" style="width:45%;background:var(--blue);"></div></div>
<div class="w-desc">Avg capacity preserved; Γ—0.6 if any breach</div>
</div>
<div class="weight-row">
<div class="w-name" style="color:var(--green);">Containment Score</div>
<div class="w-pct" style="color:var(--green);">30%</div>
<div class="bar-track"><div class="bar-fill" style="width:30%;background:var(--green);"></div></div>
<div class="w-desc">% district-days below 0.40 (skips first 2 steps)</div>
</div>
<div class="weight-row">
<div class="w-name" style="color:var(--violet);">Efficiency Score</div>
<div class="w-pct" style="color:var(--violet);">15%</div>
<div class="bar-track"><div class="bar-fill" style="width:15%;background:var(--violet);"></div></div>
<div class="w-desc">Resource actions targeting highest-infected district</div>
</div>
<div class="weight-row">
<div class="w-name" style="color:var(--amber);">Speed Score</div>
<div class="w-pct" style="color:var(--amber);">10%</div>
<div class="bar-track"><div class="bar-fill" style="width:10%;background:var(--amber);"></div></div>
<div class="w-desc">1 βˆ’ (steps / max_steps) if finished early; else 0</div>
</div>
</div>
<div class="card">
<div class="card-title">Design Decisions</div>
<div style="font-size:0.75rem;color:var(--muted);line-height:2;">
<div><span style="color:var(--blue);">β†’</span> <strong style="color:var(--text);">Hospital weighted highest</strong> β€” system collapse is catastrophic and irreversible.</div>
<div><span style="color:var(--green);">β†’</span> <strong style="color:var(--text);">Grace period</strong> β€” first 2 steps excluded from containment; initial state outside agent control.</div>
<div><span style="color:var(--violet);">β†’</span> <strong style="color:var(--text);">Pre-action efficiency</strong> β€” uses previous step's state so successful treatment isn't retroactively penalised.</div>
<div><span style="color:var(--amber);">β†’</span> <strong style="color:var(--text);">Speed as tiebreaker</strong> β€” rewards decisive proactive containment over dragging to max steps.</div>
<div><span style="color:var(--red);">β†’</span> <strong style="color:var(--text);">Breach multiplier Γ—0.6</strong> β€” any hospital collapse permanently degrades the hospital sub-score.</div>
</div>
</div>
</div>
<div class="card">
<div class="card-title">Reward Function</div>
<table class="table">
<tr><th>Term</th><th>Value</th><th>Fires When</th><th>Design Rationale</th></tr>
<tr><td>Infection penalty</td><td class="neg">βˆ’0.50 Γ— density</td><td>District infection &gt; 0.40</td><td>Dense districts penalised more; realistic triage</td></tr>
<tr><td>Hospital breach</td><td class="neg">βˆ’1.00</td><td>Hospital capacity ≀ 10%</td><td>Collapse is catastrophic; heaviest penalty</td></tr>
<tr><td>Early containment</td><td class="pos">+0.50 Γ— (1 βˆ’ t)</td><td>District infection &lt; 0.20</td><td>Decays over time; proactive action rewarded</td></tr>
<tr><td>Correct prioritisation</td><td class="pos">+0.30</td><td>Allocate to highest-infected</td><td>Rewards triage intelligence at each step</td></tr>
<tr><td>Unnecessary restriction</td><td class="neg">βˆ’0.20</td><td>Restrict district below 0.20</td><td>Penalises over-intervention on safe districts</td></tr>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════════
ARCHITECTURE
════════════════════════════════════════════════ -->
<div id="tab-architecture" class="section">
<div class="card" style="margin-bottom:1rem;">
<div class="card-title">OpenEnv Interface</div>
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;margin-top:0.25rem;">
<div style="background:var(--surface2);border-radius:8px;padding:0.6rem 1rem;font-size:0.74rem;">
<span style="color:var(--muted);">env.</span><span style="color:var(--blue);">reset</span><span style="color:var(--muted);">(task_name)</span>
<span style="color:var(--dim);margin:0 0.5rem;">β†’</span><span style="color:var(--green);">CityObservation</span>
</div>
<div style="background:var(--surface2);border-radius:8px;padding:0.6rem 1rem;font-size:0.74rem;">
<span style="color:var(--muted);">env.</span><span style="color:var(--blue);">step</span><span style="color:var(--muted);">(action)</span>
<span style="color:var(--dim);margin:0 0.5rem;">β†’</span><span style="color:var(--green);">CityObservation</span>
</div>
<div style="background:var(--surface2);border-radius:8px;padding:0.6rem 1rem;font-size:0.74rem;">
<span style="color:var(--muted);">env.</span><span style="color:var(--blue);">state</span>
<span style="color:var(--dim);margin:0 0.5rem;">β†’</span><span style="color:var(--green);">State</span>
</div>
</div>
</div>
<div class="grid-3" style="margin-bottom:1rem;">
<div class="arch-card"><div class="arch-icon">πŸ“‹</div><div class="arch-name">models.py</div><div class="arch-desc">Typed data contracts. DistrictObservation, DistrictTruth, CityState, CityObservation, ContainmentAction. Pydantic + dataclasses.</div></div>
<div class="arch-card"><div class="arch-icon">βš™οΈ</div><div class="arch-name">environment.py</div><div class="arch-desc">Core RL loop. Maintains OpenEnv State (tracking) and CityState (simulation). Agent only ever receives CityObservation.</div></div>
<div class="arch-card"><div class="arch-icon">πŸ“Š</div><div class="arch-name">grader.py</div><div class="arch-desc">Deterministic trajectory scorer. Reads hidden CityState. Four components weighted into final_score ∈ [0.0, 1.0]. Zero LLM calls.</div></div>
<div class="arch-card"><div class="arch-icon">🌊</div><div class="arch-name">utils.py</div><div class="arch-desc">SIR-inspired spread model with linear spillover (no wrap-around). Observation builder enforcing partial observability by task.</div></div>
<div class="arch-card"><div class="arch-icon">🧠</div><div class="arch-name">core/trajectory.py</div><div class="arch-desc">EpisodicMemory. Stores (obs, action, reward) tuples. Retrieves top-k by L1 distance on infection profiles, phase-weighted.</div></div>
<div class="arch-card"><div class="arch-icon">🎯</div><div class="arch-name">baseline/evaluator.py</div><div class="arch-desc">GRPO-style loop. Per-task rollouts: easy=2, medium=3, hard=3. Advantage = Rα΅’ βˆ’ mean(R). Memory threshold -0.3. Best score reported.</div></div>
</div>
<div class="card">
<div class="card-title">HTTP Endpoints</div>
<table class="table">
<tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
<tr><td style="color:var(--green);font-weight:600;">/</td><td>GET</td><td>This judge dashboard</td></tr>
<tr><td style="color:var(--green);font-weight:600;">/health</td><td>GET</td><td>Health check β€” returns {"status":"ok"}</td></tr>
<tr><td style="color:var(--green);font-weight:600;">/info</td><td>GET</td><td>Environment metadata, task config, grader weights</td></tr>
<tr><td style="color:var(--green);font-weight:600;">/grade</td><td>GET</td><td>Grader scores for the last completed episode</td></tr>
<tr><td style="color:var(--green);font-weight:600;">/validate</td><td>GET</td><td>Phase 1 automated spec compliance β€” all checks with pass/fail</td></tr>
<tr><td style="color:var(--green);font-weight:600;">/demo/{task}</td><td>GET</td><td>Rule-based greedy episode + full step log + grader score</td></tr>
</table>
</div>
</div>
</div><!-- /content -->
<script>
/* ── TAB SWITCHING ── */
function tab(name, el) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
el.classList.add('active');
}
const pct = v => (v * 100).toFixed(1) + '%';
const fmt = v => typeof v === 'number' ? (v * 100).toFixed(1) + '%' : v;
/* ── PHASE 1: VALIDATION ── */
async function runValidation() {
const btn = document.getElementById('btn-validate');
const loader = document.getElementById('load-validate');
const result = document.getElementById('validate-result');
const ph = document.getElementById('validate-placeholder');
btn.disabled = true;
loader.style.display = 'flex';
result.style.display = 'none';
ph.style.display = 'none';
try {
const r = await fetch('/validate');
const d = await r.json();
const passCount = d.pass_count;
const failCount = d.total - d.pass_count;
document.getElementById('v-overall').textContent = d.overall;
document.getElementById('v-overall').style.color = d.overall === 'PASS' ? 'var(--green)' : 'var(--red)';
document.getElementById('v-pass').textContent = passCount;
document.getElementById('v-pass').style.color = 'var(--green)';
document.getElementById('v-fail').textContent = failCount;
document.getElementById('v-fail').style.color = failCount > 0 ? 'var(--red)' : 'var(--muted)';
document.getElementById('v-total').textContent = d.total;
const container = document.getElementById('v-checks');
container.innerHTML = Object.entries(d.checks).map(([key, val]) => {
const label = key.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
const cls = val.pass ? 'pass' : 'fail';
const icon = val.pass ? 'βœ“' : 'βœ—';
return `<div class="check-item">
<div class="check-icon ${cls}">${icon}</div>
<div class="check-name">${label}</div>
<div class="check-detail">${val.detail || ''}</div>
</div>`;
}).join('');
loader.style.display = 'none';
result.style.display = 'block';
} catch (e) {
loader.style.display = 'none';
ph.style.display = 'block';
ph.textContent = 'Validation error: ' + e.message;
} finally {
btn.disabled = false;
}
}
</script>
</body>
</html>"""
def main() -> None:
import uvicorn
uvicorn.run(
"server.app:app",
host = "0.0.0.0",
port = int(os.getenv("PORT", "7860")),
reload= False,
)
if __name__ == "__main__":
main()