#!/usr/bin/env python3 import json import sys from pathlib import Path import numpy as np from dotenv import load_dotenv from rich.console import Console from rich.table import Table from rich import box load_dotenv() sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from viral_script_engine.agents.baseline_arbitrator import BaselineArbitratorAgent from viral_script_engine.environment.env import ViralScriptEnv console = Console() BASE_DIR = Path(__file__).parent.parent LOGS_DIR = BASE_DIR / "logs" LOGS_DIR.mkdir(exist_ok=True) _SCHEDULE = ( [(i, "easy") for i in range(1, 9)] + [(i, "medium") for i in range(9, 17)] + [(i, "hard") for i in range(17, 21)] ) _REWARD_KEYS = ["r1_hook_strength", "r2_coherence", "r3_cultural_alignment", "r4_debate_resolution", "r5_defender_preservation"] def _make_env(difficulty: str) -> ViralScriptEnv: return ViralScriptEnv( scripts_path=str(BASE_DIR / "data" / "test_scripts" / "scripts.json"), cultural_kb_path=str(BASE_DIR / "data" / "cultural_kb.json"), max_steps=5, difficulty=difficulty, ) def run_episode(ep_num: int, difficulty: str, agent: BaselineArbitratorAgent) -> dict: env = _make_env(difficulty) obs, _ = env.reset() episode_id = obs["episode_id"] script_id = "unknown" state = env.state() original_script = state.get("original_script", "") steps_log = [] total_reward = 0.0 for _ in range(env.max_steps): action = agent.act(obs) obs, reward, terminated, truncated, info = env.step(action) rc = info["reward_components"] anti_log = info.get("anti_gaming_log", {}) step_entry = { "r1": rc.get("r1_hook_strength"), "r2": rc.get("r2_coherence"), "r3": rc.get("r3_cultural_alignment"), "r4": rc.get("r4_debate_resolution"), "r5": rc.get("r5_defender_preservation"), "process_reward": rc.get("process_reward"), # Phase 7 — expected ~0 for untrained "total": reward, "anti_gaming_triggered": anti_log.get("triggered", False), "penalty": anti_log.get("penalty_applied", 0.0), } steps_log.append(step_entry) total_reward = reward if terminated or truncated: break final_state = env.state() final_script = final_state.get("current_script", "") return { "episode_num": ep_num, "episode_id": episode_id, "difficulty": difficulty, "script_id": script_id, "steps": steps_log, "total_reward": total_reward, "anti_gaming_logs": final_state.get("anti_gaming_logs", []), "original_script": original_script, "final_script": final_script, } def main(): agent = BaselineArbitratorAgent() all_episodes = [] for ep_num, difficulty in _SCHEDULE: console.print(f"[dim]Episode {ep_num:02d}/20 ({difficulty})...[/dim]") try: result = run_episode(ep_num, difficulty, agent) all_episodes.append(result) console.print( f" -> total_reward={result['total_reward']:.3f} " f"steps={len(result['steps'])}" ) except Exception as e: console.print(f" [red]ERROR episode {ep_num}: {e}[/red]") all_episodes.append({ "episode_num": ep_num, "episode_id": "", "difficulty": difficulty, "script_id": "error", "steps": [], "total_reward": 0.0, "anti_gaming_logs": [], "original_script": "", "final_script": "", "error": str(e), }) results_path = LOGS_DIR / "baseline_results_v2.json" with open(results_path, "w", encoding="utf-8") as f: json.dump(all_episodes, f, indent=2, default=str) _save_plots(all_episodes) _print_summary(all_episodes) mean_total = float(np.mean([e["total_reward"] for e in all_episodes])) console.print( f"\n[bold green]PHASE 2 GATE: PASS — Baseline curves saved. " f"Pre-training mean total reward: {mean_total:.2f}[/bold green]" ) def _collect_reward_series(episodes: list, key: str): series = [] for ep in episodes: vals = [s.get(key) for s in ep.get("steps", []) if s.get(key) is not None] series.append(vals[-1] if vals else 0.0) return series def _save_plots(episodes: list): import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt labels = { "r1": "R1 Hook Strength", "r2": "R2 Coherence", "r3": "R3 Cultural Alignment", "r4": "R4 Debate Resolution", "r5": "R5 Defender Preservation", "total": "Total Reward", } keys = list(labels.keys()) ep_nums = [e["episode_num"] for e in episodes] fig, axes = plt.subplots(2, 3, figsize=(14, 8), dpi=150) fig.suptitle( "Baseline (Untrained) Arbitrator — Pre-Training Reward Curves", fontsize=13, ) for idx, key in enumerate(keys): ax = axes[idx // 3][idx % 3] series = _collect_reward_series(episodes, key) if key != "total" else [e["total_reward"] for e in episodes] ax.plot(ep_nums, series, marker="o", linewidth=1.5, markersize=4) ax.set_title(labels[key], fontsize=10) ax.set_xlabel("Episode", fontsize=8) ax.set_ylabel("Reward", fontsize=8) ax.set_ylim(0, 1) ax.set_xlim(min(ep_nums) - 0.5, max(ep_nums) + 0.5) ax.tick_params(labelsize=7) ax.grid(True, alpha=0.3) plt.tight_layout() plot_path = LOGS_DIR / "baseline_reward_curves.png" plt.savefig(str(plot_path), dpi=150) plt.close() console.print(f"[dim]Curves saved -> {plot_path}[/dim]") def _print_summary(episodes: list): table = Table(title="Baseline Results — Mean +/- Std (20 episodes)", box=box.SIMPLE_HEAD) table.add_column("Reward", style="cyan", min_width=28) table.add_column("Mean", min_width=8) table.add_column("Std", min_width=8) table.add_column("Min", min_width=8) table.add_column("Max", min_width=8) label_map = { "r1": "R1 Hook Strength", "r2": "R2 Coherence", "r3": "R3 Cultural Alignment", "r4": "R4 Debate Resolution", "r5": "R5 Defender Preservation", "total": "Total Reward", } for key, label in label_map.items(): if key == "total": vals = [e["total_reward"] for e in episodes] else: vals = _collect_reward_series(episodes, key) arr = np.array(vals, dtype=float) table.add_row( label, f"{arr.mean():.3f}", f"{arr.std():.3f}", f"{arr.min():.3f}", f"{arr.max():.3f}", ) console.print(table) if __name__ == "__main__": main()