Gridmind / scripts /full_demo.py
adityss's picture
feat: add scripts/full_demo.py — unified 10-step demo proving all 4 hackathon themes operational
5636c9d
#!/usr/bin/env python3
"""
GridMind-RL Full Unified Demo — All 4 Themes Running Together
-------------------------------------------------------------
This single script demonstrates all hackathon themes in one flow.
Each step proves a theme and prints WHY it matters for judges.
Themes:
Theme 1 — Multi-Agent: 3 buildings + coordinator + /feeder
Theme 2 — Long-Horizon Planning: Task 4 instruction cards over 96 steps
Theme 3 — World Modeling: /simulate before committing actions
Theme 4 — Self-Improvement: Curriculum auto-advances task difficulty
Run: python scripts/full_demo.py
"""
from __future__ import annotations
import json
import sys
import time
import requests
ENV_URL = "http://localhost:7860"
def bold(text: str) -> str:
return f"**{text}**"
def section(n: int, title: str, theme: str) -> None:
print(f"\n{'='*62}")
print(f" STEP {n}{bold(title)} [{theme}]")
print(f"{'='*62}")
def check_pass(text: str) -> None:
print(f" [OK] {text}")
def check_warn(text: str) -> None:
print(f" [!] {text}")
def show_info(label: str, value: str) -> None:
print(f" {label}: {value}")
def r(path: str, method="GET", json_data=None) -> dict:
url = f"{ENV_URL}{path}"
try:
if method == "GET":
resp = requests.get(url, timeout=10)
else:
resp = requests.post(url, json=json_data, timeout=10)
resp.raise_for_status()
return resp.json()
except Exception as e:
check_warn(f"Request failed: {e}")
return {}
def step(n: int, title: str, theme: str, fn):
section(n, title, theme)
result = fn()
if result is not False:
check_pass("Passed")
return result
# ── STEP 1 — Health Check ──────────────────────────────────────────────
def do_step1():
data = r("/health")
show_info("Status", data.get("status", "?") + " OK" if data.get("status") == "ok" else data.get("status", "?"))
show_info("Version", data.get("version", "?"))
return data.get("status") == "ok"
# ── STEP 2 — OpenEnv /info Metadata ──────────────────────────────────
def do_step2():
data = r("/info")
show_info("Name", data.get("name", "?"))
show_info("Version", data.get("version", "?"))
themes = data.get("themes", [])
show_info("Themes", ", ".join(themes))
endpoints = data.get("endpoints", [])
show_info("Endpoints", f"{len(endpoints)} registered")
print(f" Sample endpoints: {endpoints[:5]}")
return len(themes) >= 4 and "multi-agent" in themes
# ── STEP 3 — All 4 Tasks Available ────────────────────────────────────
def do_step3():
data = r("/tasks")
tasks = data if isinstance(data, list) else data.get("tasks", [])
print(f" {len(tasks)} tasks available:")
for t in tasks:
print(f" Task {t['id']}: {t['name']} ({t['difficulty']})")
ids = [t["id"] for t in tasks]
return 1 in ids and 2 in ids and 3 in ids and 4 in ids
# ── STEP 4 — Theme 1: Multi-Agent Reset ───────────────────────────────
def do_step4():
data = r("/reset", "POST", {"task_id": 3, "num_buildings": 3, "seed": 42})
obs_list = data.get("observations", [])
print(f" Buildings in federation: {len(obs_list)}")
for b in obs_list:
tid = b.get("building_id", 0)
temp = round(b.get("indoor_temperature", 0), 1)
storage = round(b.get("thermal_storage_level", 0) * 100)
cost = round(b.get("cumulative_cost", 0), 2)
print(f" Building {tid}: {temp}C | storage {storage}% | cost ${cost}")
return len(obs_list) == 3
# ── STEP 5 — Theme 1: Fleet-wide Feeder View ─────────────────────────
def do_step5():
data = r("/feeder")
total = round(data.get("total_demand_kw", 0), 1)
limit = data.get("feeder_limit_kw", 0)
overload = data.get("feeder_overload", False)
util = round(data.get("utilization_pct", 0), 1)
buildings = data.get("buildings", [])
feeder_status = "OK" if not overload else "OVERLOAD"
print(f" Feeder: {total} / {limit} kW [{feeder_status}]")
print(f" Utilization: {util}%")
for b in buildings:
bid = b.get("building_id", "?")
dem = round(b.get("current_demand_kw", 0), 1)
pm = b.get("price_multiplier", 1.0)
print(f" Building {bid}: {dem} kW | price mult {pm}x")
return total > 0 and len(buildings) == 3
# ── STEP 6 — Theme 1: Coordinator Price Signals ───────────────────────
def do_step6():
multipliers = [1.5, 0.8, 1.0]
r("/coordinate", "POST", {"price_multipliers": multipliers})
print(f" Set price signals: {multipliers}")
feeder = r("/feeder")
blds = feeder.get("buildings", [])
for i, b in enumerate(blds):
pm = b.get("price_multiplier", "?")
signal = ">> expensive (will conserve)" if pm > 1.2 else "<< cheap (can charge)"
print(f" Building {i}: {pm}x | {signal}")
return True
# ── STEP 7 — Theme 3: World Modeling /simulate ───────────────────────
def do_step7():
action = {
"hvac_power_level": 0.8,
"thermal_charge_rate": 0.5,
"batch_job_slot": 0,
"load_shed_fraction": 0.0,
"building_id": 0,
}
# Check step counter before simulate
state_before = r("/state")
step_before = state_before.get("step", "?")
print(f" Step before simulate: {step_before}")
sim_data = r("/simulate", "POST", [action])
results = sim_data.get("results", [])
print(f" Simulated 1 action ahead")
if results:
r0 = results[0]
obs = r0.get("observation", {})
reward = round(r0.get("reward", 0), 3)
print(f" Predicted reward: {reward}")
print(f" Predicted temp: {round(obs.get('indoor_temperature', 0), 1)}C")
print(f" Predicted storage: {round(obs.get('thermal_storage_level', 0) * 100)}%")
print(f" Would episode end? {sim_data.get('done', False)}")
# Verify step did NOT advance
state_after = r("/state")
step_after = state_after.get("step", "?")
if step_before == step_after:
check_pass(f"World model confirmed: step unchanged ({step_before})")
return True
else:
check_warn(f"Step changed from {step_before} to {step_after}")
return False
# ── STEP 8 — Take a Step + Check Fault System ─────────────────────────
def do_step8():
action = {
"hvac_power_level": 0.7,
"thermal_charge_rate": 0.0,
"batch_job_slot": 0,
"load_shed_fraction": 0.0,
"building_id": 0,
}
data = r("/step", "POST", [action])
if isinstance(data, list):
results = data
else:
results = data.get("results", []) if isinstance(data, dict) else []
if not results:
print(" Warning: /step returned unexpected format")
return False
obs = results[0].get("observation", {})
step_num = obs.get("step", "?")
faults = obs.get("active_faults", [])
reward = round(results[0].get("reward", 0), 3)
show_info("Step", str(step_num))
show_info("Reward", str(reward))
show_info("Active faults", f"{len(faults)}")
if faults:
for f in faults:
print(f" [!] FAULT: {f}")
else:
print(" No faults triggered yet (expected on hard difficulty)")
return True
# ── STEP 9 — Theme 2: Task 4 Instruction Card ───────────────────────────
def do_step9():
data = r("/reset", "POST", {"task_id": 4, "seed": 99})
obs_list = data.get("observations", [])
if not obs_list:
check_warn("No observations returned")
return False
obs = obs_list[0]
card = obs.get("instruction_card") or {}
card_text = card.get("text", "")
targets = card.get("targets", {})
print(f" Task 4: Instruction Following")
print(f" Objective: {card_text[:120]}...")
if targets:
print(f" Targets:")
for k, v in targets.items():
print(f" {k}: {v}")
return bool(card_text)
# ── STEP 10 — Theme 4: /grade + Reward Decomposition ──────────────────
def do_step10():
# First run a full episode (simplified: 10 steps)
r("/reset", "POST", {"task_id": 3, "num_buildings": 3, "seed": 42})
for _ in range(10):
action = {
"hvac_power_level": 0.6,
"thermal_charge_rate": 0.0,
"batch_job_slot": 0,
"load_shed_fraction": 0.0,
"building_id": 0,
}
r("/step", "POST", [action])
grade_data = r("/grade")
score = grade_data.get("score", "?")
exploit = grade_data.get("exploit_detected", False)
penalty = grade_data.get("penalty_applied", 0)
sub = grade_data.get("sub_scores", {})
show_info("Episode score", f"{score} (0.0-1.0 clamped)" if score != "?" else "?")
show_info("Exploit detected", str(exploit))
show_info("Penalty applied", str(penalty))
if sub:
print(" Reward components:")
for k, v in sub.items():
print(f" {k}: {round(v, 3)}")
show_info("Task ID", str(grade_data.get("task_id", "?")))
return score not in ("?", None) and 0.0 < float(score) < 1.0
# ── Main ────────────────────────────────────────────────────────────────
def main():
print(bold("\nGridMind-RL — Full Unified Demo"))
print(f" Environment: {ENV_URL}")
print(f" Themes: Multi-Agent | Long-Horizon | World Modeling | Self-Improvement")
results = []
results.append(step(1, "Health Check", "Foundation", do_step1))
results.append(step(2, "OpenEnv /info Metadata", "Foundation", do_step2))
results.append(step(3, "All 4 Tasks Available", "Foundation", do_step3))
results.append(step(4, "Multi-Agent Reset (3 Buildings)", "Theme 1: Multi-Agent", do_step4))
results.append(step(5, "Fleet-wide Feeder View", "Theme 1: Multi-Agent", do_step5))
results.append(step(6, "Coordinator Price Signals", "Theme 1: Multi-Agent", do_step6))
results.append(step(7, "World Modeling /simulate", "Theme 3: World Modeling", do_step7))
results.append(step(8, "Step + Fault Events", "Wild Card: Fault Resilience", do_step8))
results.append(step(9, "Task 4 Instruction Card", "Theme 2: Long-Horizon", do_step9))
results.append(step(10, "Episode /grade + Reward Decomposition", "Theme 4: Self-Improvement", do_step10))
sep = "=" * 62
print(f"\n{bold(sep)}")
print(f" SUMMARY — All Themes Check")
print(sep)
passed = sum(1 for r in results if r)
total = len(results)
print(f" Passed: {passed}/{total}")
if passed == total:
print(f" [OK] ALL THEMES OPERATIONAL")
else:
failed = [i + 1 for i, r in enumerate(results) if not r]
print(f" [FAIL] Failed steps: {failed}")
print()
if __name__ == "__main__":
main()