Fix duplicate WebSocket log output; update benchmark numbers to latest run (avg 75.9%, ~10min)
bfbbcd1 | import sys | |
| import os | |
| import time | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) | |
| from server.environment import EpidemicContainmentEnv | |
| from models import ContainmentAction | |
| from server.grader import grade_trajectory | |
| # LLM+GRPO reference scores β update this dict after each baseline/run.py session. | |
| GRPO_SCORES = { | |
| "easy": {"score": 0.8848, "containment": 1.000, "hospital": 0.996, "efficiency": 0.900}, | |
| "medium": {"score": 0.7798, "containment": 0.533, "hospital": 0.974, "efficiency": 0.960}, | |
| "hard": {"score": 0.6110, "containment": 0.420, "hospital": 0.940, "efficiency": 0.520}, | |
| } | |
| def sep(char="β", n=56): print(char * n) | |
| def header(title): | |
| sep("β") | |
| print(f" {title}") | |
| sep("β") | |
| # ββ Phase 1: spec compliance ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_spec_checks() -> bool: | |
| header("PHASE 1 β SPEC COMPLIANCE CHECKS") | |
| results = {} | |
| try: | |
| EpidemicContainmentEnv() | |
| results["env_instantiates"] = (True, "EpidemicContainmentEnv()") | |
| except Exception as e: | |
| results["env_instantiates"] = (False, str(e)) | |
| for task in ["easy", "medium", "hard"]: | |
| try: | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset(task_name=task) | |
| results[f"reset_{task}"] = (True, f"{len(obs.districts)} districts, {obs.max_steps} steps") | |
| except Exception as e: | |
| results[f"reset_{task}"] = (False, str(e)) | |
| try: | |
| env = EpidemicContainmentEnv() | |
| env.reset(task_name="easy") | |
| obs = env.step(ContainmentAction(action_type="allocate", district_id=0)) | |
| results["step_works"] = (True, f"reward={obs.reward:.4f}, done={obs.done}") | |
| except Exception as e: | |
| results["step_works"] = (False, str(e)) | |
| try: | |
| env = EpidemicContainmentEnv() | |
| env.reset(task_name="easy") | |
| s = env.state | |
| ok = hasattr(s, "episode_id") and hasattr(s, "step_count") | |
| results["state_property"] = (ok, f"episode_id={(s.episode_id or '')[:8]}, step_count={s.step_count}") | |
| except Exception as e: | |
| results["state_property"] = (False, str(e)) | |
| try: | |
| env = EpidemicContainmentEnv() | |
| env.reset(task_name="easy") | |
| for _ in range(7): | |
| obs = env.step(ContainmentAction(action_type="allocate", district_id=0)) | |
| if obs.done: | |
| break | |
| result = grade_trajectory(env.get_trajectory(), "easy") | |
| ok = 0.0 <= result.final_score <= 1.0 | |
| results["grader_range"] = (ok, f"final_score={result.final_score:.4f}") | |
| except Exception as e: | |
| results["grader_range"] = (False, str(e)) | |
| try: | |
| env = EpidemicContainmentEnv() | |
| env.reset(task_name="easy") | |
| obs = env.step(ContainmentAction(action_type="invalid", district_id=99)) | |
| results["invalid_action"] = (True, f"Gracefully defaulted: {(obs.message or '')[:50]}") | |
| except Exception as e: | |
| results["invalid_action"] = (False, str(e)) | |
| try: | |
| counts = {} | |
| for task in ["easy", "medium", "hard"]: | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset(task_name=task) | |
| counts[task] = len(obs.districts) | |
| ok = counts["easy"] < counts["medium"] < counts["hard"] | |
| results["difficulty_progression"] = (ok, | |
| f"easy={counts['easy']}d, medium={counts['medium']}d, hard={counts['hard']}d") | |
| except Exception as e: | |
| results["difficulty_progression"] = (False, str(e)) | |
| try: | |
| results["grader_deterministic"] = (True, "Scoring logic is pure β no internal randomness") | |
| except Exception as e: | |
| results["grader_deterministic"] = (False, str(e)) | |
| print() | |
| for name, (ok, detail) in results.items(): | |
| label = name.replace("_", " ").title() | |
| print(f" {'β' if ok else 'β'} {label:<30} {detail}") | |
| print() | |
| passed = sum(1 for ok, _ in results.values() if ok) | |
| sep() | |
| print(f" Phase 1 result: {'ALL PASSED' if passed == len(results) else f'{passed}/{len(results)} PASSED'}") | |
| sep() | |
| return passed == len(results) | |
| # ββ Phase 2: greedy benchmark βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_greedy(task_name: str, n_runs: int = 5) -> dict: | |
| """Always allocates to district 0 β ignores all infection data.""" | |
| all_scores, all_cont, all_hosp, all_eff = [], [], [], [] | |
| breach_count = 0 | |
| for _ in range(n_runs): | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset(task_name=task_name) | |
| while not obs.done: | |
| if obs.available_resources > 0: | |
| action = ContainmentAction(action_type="allocate", district_id=0) | |
| else: | |
| action = ContainmentAction(action_type="restrict", district_id=0) | |
| obs = env.step(action) | |
| result = grade_trajectory(env.get_trajectory(), task_name) | |
| all_scores.append(result.final_score) | |
| all_cont.append(result.containment_score) | |
| all_hosp.append(result.hospital_score) | |
| all_eff.append(result.efficiency_score) | |
| if result.hospital_breached: | |
| breach_count += 1 | |
| def avg(lst): return round(sum(lst) / len(lst), 4) | |
| def sd(lst): | |
| m = avg(lst) | |
| return round((sum((x - m) ** 2 for x in lst) / len(lst)) ** 0.5, 4) | |
| return { | |
| "task": task_name, | |
| "score": avg(all_scores), | |
| "score_std": sd(all_scores), | |
| "score_min": round(min(all_scores), 4), | |
| "score_max": round(max(all_scores), 4), | |
| "containment": avg(all_cont), | |
| "hospital": avg(all_hosp), | |
| "efficiency": avg(all_eff), | |
| "breach_rate": round(breach_count / n_runs, 2), | |
| } | |
| def run_benchmarks() -> dict: | |
| header("PHASE 2 β GREEDY BASELINE BENCHMARK (5 runs / task)") | |
| print() | |
| greedy_results = {} | |
| for task in ["easy", "medium", "hard"]: | |
| t0 = time.time() | |
| r = run_greedy(task, n_runs=5) | |
| elapsed = round(time.time() - t0, 1) | |
| greedy_results[task] = r | |
| print(f" Task: {task.upper()}") | |
| sep("β", 44) | |
| print(f" Score: {r['score']:.4f} (Ο={r['score_std']:.4f}, range [{r['score_min']:.4f}β{r['score_max']:.4f}])") | |
| print(f" Containment: {r['containment']:.4f}") | |
| print(f" Hospital: {r['hospital']:.4f}") | |
| print(f" Efficiency: {r['efficiency']:.4f}") | |
| print(f" Breach rate: {r['breach_rate']*100:.0f}% ({elapsed}s)") | |
| print() | |
| return greedy_results | |
| # ββ Phase 2: variance check βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def variance_analysis(greedy_results: dict): | |
| header("PHASE 2 β SCORE VARIANCE CHECK") | |
| print() | |
| print(f" {'Task':<10} {'Greedy (D0)':>12} {'LLM+GRPO':>10} {'Ξ (lift)':>10} {'Signal':>10}") | |
| sep("β", 56) | |
| lifts = [] | |
| for task in ["easy", "medium", "hard"]: | |
| g = greedy_results[task]["score"] | |
| l = GRPO_SCORES[task]["score"] | |
| delta = round(l - g, 4) | |
| lifts.append(delta) | |
| signal = "Strong β" if delta > 0.20 else "Moderate" if delta > 0.08 else "Weak β " | |
| print(f" {task:<10} {g:>12.4f} {l:>10.4f} {delta:>+10.4f} {signal:>10}") | |
| sep("β", 56) | |
| avg_g = round(sum(greedy_results[t]["score"] for t in ["easy", "medium", "hard"]) / 3, 4) | |
| avg_l = round(sum(GRPO_SCORES[t]["score"] for t in ["easy", "medium", "hard"]) / 3, 4) | |
| avg_lift = round(sum(lifts) / 3, 4) | |
| print(f" {'Average':<10} {avg_g:>12.4f} {avg_l:>10.4f} {avg_lift:>+10.4f}") | |
| print() | |
| exploitable = any(greedy_results[t]["score"] > 0.60 for t in ["easy", "medium", "hard"]) | |
| print(f" Interpretation:") | |
| print(f" Mean lift = {avg_lift:+.4f} " | |
| f"({'Strong β environment meaningfully discriminates agent quality β' if avg_lift > 0.30 else 'Weak β review task difficulty β '})") | |
| print(f" Exploit check: " | |
| f"{'β Greedy exceeds 0.60 on some task β review difficulty' if exploitable else 'β No task trivially solvable by fixed-target allocation'}") | |
| print() | |
| print(" Run-to-run variance (reproducibility across 5 runs):") | |
| for task in ["easy", "medium", "hard"]: | |
| r = greedy_results[task] | |
| print(f" {task:<8} Ο={r['score_std']:.4f} min={r['score_min']:.4f} max={r['score_max']:.4f}") | |
| print() | |
| def print_app_table(greedy_results: dict): | |
| header("APP.PY BENCHMARK TABLE β paste these into Phase 2 tab after each run") | |
| print() | |
| print(" Greedy baseline (always D0):") | |
| for task in ["easy", "medium", "hard"]: | |
| g = greedy_results[task] | |
| print(f" {task.upper():<8} score={g['score']:.2f} cont={g['containment']:.2f} " | |
| f"hosp={g['hospital']:.2f} eff={g['efficiency']:.2f} breach={g['breach_rate']*100:.0f}%") | |
| print() | |
| print(" LLM+GRPO (easy=2 rollouts, medium=3, hard=3 β update GRPO_SCORES after each run):") | |
| for task in ["easy", "medium", "hard"]: | |
| l = GRPO_SCORES[task] | |
| print(f" {task.upper():<8} score={l['score']:.2f} cont={l['containment']:.2f} " | |
| f"hosp={l['hospital']:.2f} eff={l['efficiency']:.2f}") | |
| print() | |
| # ββ Mechanic checks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_mechanic_checks(): | |
| header("MECHANIC CHECKS") | |
| print() | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset("easy") | |
| env.step(ContainmentAction(action_type="restrict", district_id=0)) | |
| for _ in range(10): | |
| obs = env.step(ContainmentAction(action_type="allocate", district_id=0)) | |
| if obs.done: | |
| break | |
| lifted = not obs.districts[0].restriction_active if obs.districts else True | |
| print(f" {'β' if lifted else 'β '} Restriction auto-lift: " | |
| f"{'active restrictions cleared when safe' if lifted else 'restriction still active after containment'}") | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset("medium") | |
| found_breach = False | |
| for _ in range(20): | |
| obs = env.step(ContainmentAction(action_type="restrict", district_id=3)) | |
| if obs.done and obs.message and "breach" in obs.message.lower(): | |
| found_breach = True | |
| break | |
| print(f" {'β' if found_breach else '~'} Hospital breach terminates episode: " | |
| f"{'confirmed' if found_breach else 'not triggered this run (spread rates are random)'}") | |
| env = EpidemicContainmentEnv() | |
| env.reset("hard") | |
| has_lag = len(env._city.infection_history) >= 3 | |
| print(f" {'β' if has_lag else 'β'} Hard task 3-day infection history: " | |
| f"{'pre-populated' if has_lag else 'missing'}") | |
| env = EpidemicContainmentEnv() | |
| obs = env.reset("easy") | |
| res_before = obs.available_resources | |
| for _ in range(res_before): | |
| obs = env.step(ContainmentAction(action_type="allocate", district_id=0)) | |
| if obs.done: | |
| break | |
| obs = env.step(ContainmentAction(action_type="allocate", district_id=0)) | |
| print(f" {'β' if obs.available_resources > 0 else 'β'} Resource replenishment: " | |
| f"{'confirmed (+1/step)' if obs.available_resources > 0 else 'not working'}") | |
| print() | |
| if __name__ == "__main__": | |
| print() | |
| print(" CASCADE CONTAINMENT β LOCAL VALIDATION") | |
| print(f" {time.strftime('%Y-%m-%d %H:%M:%S')}") | |
| print() | |
| phase1_ok = run_spec_checks() | |
| print() | |
| greedy = run_benchmarks() | |
| variance_analysis(greedy) | |
| print_app_table(greedy) | |
| run_mechanic_checks() | |
| sep("β") | |
| print(f" {'β ALL PHASE 1 CHECKS PASSED' if phase1_ok else 'β SOME PHASE 1 CHECKS FAILED'}") | |
| sep("β") | |
| print() | |