Akshay Babbar
chore: HF Space export (size filter)
98a5a8c
from __future__ import annotations
import html
import math
import re
from typing import Any, Dict, List, Optional, Tuple
from budget_router.reward import grade_episode
from budget_router.tasks import TASK_PRESETS
from .config import MAX_STEPS
# ─── Grade Computation ────────────────────────────────────────────────────────
MISSION_SCORE_LABEL = "Benchmark Score"
MISSION_SCORE_HELP = "Headline evaluation metric: weighted success, latency, budget, SLA, and adaptation."
def compute_grade(history: List[Dict]) -> Dict[str, float]:
canonical_history = [
{
"step": h.get("step", 0),
"action_type": h.get("action", "shed_load"),
"request_succeeded": h.get("succeeded", False),
"cost": h.get("cost", 0.0),
"latency_ms": h.get("latency_ms", 0.0),
"reward": h.get("reward", 0.0),
"sla_ceiling_ms": h.get("sla_ceiling_ms", 500.0),
"initial_budget": h.get("initial_budget", 1.0),
"degradation_start_step": h.get("degradation_start_step", 999),
"secondary_degradation_start_step": h.get("secondary_degradation_start_step"),
}
for h in history
]
return grade_episode(canonical_history)
# ─── HTML Renderers ───────────────────────────────────────────────────────────
_TABLE_STYLE = "width:100%;border-collapse:collapse;font-size:13px;background:#ffffff;color:#111827"
_HEADER_ROW_STYLE = "background:#fafafa"
_HEADER_CELL_STYLE = "border:1px solid #eee;padding:6px;color:#111827"
_CELL_STYLE = "border:1px solid #eee;padding:6px;text-align:center;color:#111827;background:#ffffff"
_ACTION_CELL_STYLE = "border:1px solid #eee;padding:6px;font-weight:600;color:#111827"
def _join(parts: List[str]) -> str:
return "".join(parts)
def _th(value: str) -> str:
return f"<th style='{_HEADER_CELL_STYLE}'>{value}</th>"
def _td(value: str, style: str, colspan: Optional[int] = None) -> str:
span = f" colspan='{colspan}'" if colspan else ""
return f"<td{span} style='{style}'>{value}</td>"
def _tr(cells: List[str], style: Optional[str] = None) -> str:
style_attr = f" style='{style}'" if style else ""
return f"<tr{style_attr}>" + "".join(cells) + "</tr>"
def _table(head: str, body: str) -> str:
return f"<table style='{_TABLE_STYLE}'><thead>{head}</thead><tbody>{body}</tbody></table>"
def _bar(value: float, label: str, color: str, show_dollar: bool = False) -> str:
pct = max(0, min(100, int(value * 100)))
display = f"${value:.2f}" if show_dollar else f"{pct}%"
return (
f'<div style="margin:5px 0;color:#111827">'
f'<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:2px;color:#111827">'
f'<b>{label}</b><span>{display}</span></div>'
f'<div style="background:#e0e0e0;border-radius:5px;height:20px">'
f'<div style="width:{pct}%;background:{color};border-radius:5px;height:100%;'
f'transition:width 0.3s ease"></div></div></div>'
)
def _budget_color(remaining: float) -> str:
if remaining > 0.5:
return "#27ae60"
if remaining > 0.2:
return "#f39c12"
return "#e74c3c"
def _format_percent_precise(value: float) -> str:
# Show up to 4 decimal places without forcing integer rounding.
return f"{value * 100:.4f}".rstrip("0").rstrip(".") + "%"
def render_providers(obs: Dict) -> str:
return _join(
[
_bar(obs.get("provider_a_status", 0), "Provider A", "#27ae60"),
_bar(obs.get("provider_b_status", 0), "Provider B", "#e67e22"),
_bar(obs.get("provider_c_status", 0), "Provider C", "#e74c3c"),
]
)
def render_budget(obs: Dict) -> str:
b = obs.get("budget_remaining", 1.0)
return _bar(b, "Budget Remaining", _budget_color(b))
def render_grader(grade: Dict) -> str:
o = grade["overall_score"]
color = "#27ae60" if o > 0.7 else "#f39c12" if o > 0.4 else "#e74c3c"
return (
f'<div style="text-align:center;margin-top:12px;padding:10px;border-radius:8px;'
f'background:rgba(0,0,0,0.04)">'
f'<div style="font-size:28px;font-weight:bold;color:{color}">'
f'{MISSION_SCORE_LABEL}: {_format_percent_precise(float(o))}</div>'
f'<div style="font-size:12px;color:#6b7280;margin-top:3px">{MISSION_SCORE_HELP}</div>'
f'</div>'
)
def _GRADER_PENDING() -> str:
return "<div style='color:#aaa;font-style:italic'>Shown when episode completes.</div>"
def _REWARD_PENDING() -> str:
return "<div style='color:#aaa;font-style:italic'>Shown when episode completes.</div>"
def _PROVIDER_EMPTY() -> str:
return "<div style='color:#aaa;font-style:italic'>Start an episode to see provider health.</div>"
def render_episode_total_reward(history: List[Dict]) -> str:
total = 0.0
for h in history or []:
total += float(h.get("reward", 0.0) or 0.0)
color = "#27ae60" if total >= 0 else "#e74c3c"
return (
f'<div style="text-align:center;font-size:28px;font-weight:bold;'
f'color:{color};margin-top:12px;padding:8px;border-radius:8px;'
f'background:rgba(0,0,0,0.04)">Total Reward: {total:+.2f}</div>'
)
def _task_config(scenario_name: str):
return TASK_PRESETS.get(scenario_name, TASK_PRESETS["easy"])
def _common_provider_health(config: Any, step: int) -> Dict[str, float]:
health = {
"A": float(getattr(config, "reliability_a", 0.0) or 0.0),
"B": float(getattr(config, "reliability_b", 0.0) or 0.0),
"C": float(getattr(config, "reliability_c", 0.0) or 0.0),
}
start_raw = getattr(config, "degradation_start_step", 999)
start = 999 if start_raw is None else int(start_raw)
target = str(getattr(config, "degradation_target", "A") or "A")
rate = float(getattr(config, "degradation_rate", 0.0) or 0.0)
if start < 999 and step >= start:
count = step if start == 0 else max(0, step - start + 1)
health[target] = max(0.05, float(health.get(target, 1.0)) - rate * count)
secondary_start = getattr(config, "secondary_degradation_start_step", 999)
secondary_target = str(getattr(config, "secondary_degradation_target", "") or "")
secondary_rate = float(getattr(config, "secondary_degradation_rate", 0.0) or 0.0)
if (
secondary_target
and secondary_start is not None
and int(secondary_start) < 999
and step >= int(secondary_start)
):
count2 = max(0, step - int(secondary_start) + 1)
health[secondary_target] = max(
0.05, float(health.get(secondary_target, 1.0)) - secondary_rate * count2
)
return health
def render_common_providers(health: Dict[str, float]) -> str:
return _join(
[
_bar(float(health.get("A", 0.0)), "Provider A", "#27ae60"),
_bar(float(health.get("B", 0.0)), "Provider B", "#e67e22"),
_bar(float(health.get("C", 0.0)), "Provider C", "#e74c3c"),
]
)
def _pct(n: int, d: int) -> float:
if d <= 0:
return 0.0
return 100.0 * (n / d)
def _summary_metrics(history: List[Dict]) -> Dict[str, float]:
routing = [h for h in history if h.get("action") != "shed_load"]
failures = sum(1 for h in routing if not bool(h.get("succeeded", False)))
breaches = sum(
1
for h in routing
if float(h.get("latency_ms", 0.0) or 0.0)
> float(h.get("sla_ceiling_ms", 500.0) or 500.0)
)
latencies = [float(h.get("latency_ms", 0.0) or 0.0) for h in routing]
avg_latency = (sum(latencies) / len(latencies)) if latencies else 0.0
return {
"failed_pct": _pct(failures, len(routing)),
"sla_breach_pct": _pct(breaches, len(routing)),
"avg_latency_ms": avg_latency,
}
def _is_finite_number(value: Any) -> bool:
if isinstance(value, bool):
return False
if not isinstance(value, (int, float)):
return False
return math.isfinite(float(value))
def _fmt_pct(ok: int, total: int) -> str:
if total <= 0:
return "—"
return f"{(100.0 * ok / total):.1f}% ({ok}/{total})"
def render_data_quality_panel(history: List[Dict]) -> str:
try:
if not history:
return (
"<details style='margin-top:10px'>"
"<summary style='cursor:pointer;font-weight:600;color:#111827'>Data quality (optional)</summary>"
"<div style='color:#6b7280;margin-top:6px;font-style:italic'>No steps yet.</div>"
"</details>"
)
schema_failed: List[Tuple[int, str]] = []
consistency_failed: List[Tuple[int, str]] = []
prev_budget: Optional[float] = None
for idx, h in enumerate(history):
step_raw = h.get("step", idx + 1)
try:
step = int(step_raw)
except Exception:
step = idx + 1
action = h.get("action")
budget = h.get("budget")
meta_raw = h.get("meta_raw")
schema_errs: List[str] = []
if action not in {"route_to_a", "route_to_b", "route_to_c", "shed_load"}:
schema_errs.append(f"invalid action={action!r}")
if not isinstance(meta_raw, dict):
schema_errs.append("meta_raw missing/invalid")
meta_raw = {}
required_meta_keys = {
"step",
"action_type",
"request_succeeded",
"cost",
"latency_ms",
"sla_ceiling_ms",
"initial_budget",
"degradation_start_step",
}
missing_meta = [k for k in sorted(required_meta_keys) if k not in meta_raw]
if missing_meta:
schema_errs.append("missing meta: " + ", ".join(missing_meta))
meta_action = meta_raw.get("action_type")
if meta_action is not None and meta_action != action:
schema_errs.append(f"meta action_type={meta_action!r} != action={action!r}")
meta_step = meta_raw.get("step")
try:
meta_step_i = int(meta_step) if meta_step is not None else None
except Exception:
meta_step_i = None
schema_errs.append("meta step not int")
if meta_step_i is not None and meta_step_i != step:
schema_errs.append(f"meta step={meta_step_i} != history step={step}")
succeeded_raw = meta_raw.get("request_succeeded")
cost_raw = meta_raw.get("cost")
latency_raw = meta_raw.get("latency_ms")
sla_raw = meta_raw.get("sla_ceiling_ms")
init_b_raw = meta_raw.get("initial_budget")
if not isinstance(succeeded_raw, bool):
schema_errs.append("meta request_succeeded not bool")
if not _is_finite_number(cost_raw) or float(cost_raw) < 0:
schema_errs.append("meta cost not finite >= 0")
if not _is_finite_number(latency_raw) or float(latency_raw) < 0:
schema_errs.append("meta latency_ms not finite >= 0")
if not _is_finite_number(sla_raw) or float(sla_raw) <= 0:
schema_errs.append("meta sla_ceiling_ms not finite > 0")
if not _is_finite_number(init_b_raw) or float(init_b_raw) <= 0:
schema_errs.append("meta initial_budget not finite > 0")
if not _is_finite_number(budget) or float(budget) < 0:
schema_errs.append("budget not finite >= 0")
if schema_errs:
schema_failed.append((step, "; ".join(schema_errs)))
continue
b = float(budget)
c = float(cost_raw)
ib = float(init_b_raw)
consistency_errs: List[str] = []
if step != (idx + 1):
consistency_errs.append(f"step mismatch: got {step}, expected {idx + 1}")
if b > 1.0 + 1e-6:
consistency_errs.append("budget > 1.0")
if action == "shed_load":
if succeeded_raw is not False:
consistency_errs.append("shed_load succeeded must be False")
if abs(c - 0.0) > 1e-6:
consistency_errs.append("shed_load cost must be 0")
if abs(float(latency_raw) - 0.0) > 1e-6:
consistency_errs.append("shed_load latency_ms must be 0")
if prev_budget is not None:
if action == "shed_load":
expected = prev_budget
else:
burn = (c / ib) if ib > 0 else 0.0
expected = max(0.0, prev_budget - burn)
if abs(b - expected) > 1e-4:
consistency_errs.append(
f"budget mismatch: got {b:.4f}, expected {expected:.4f}"
)
prev_budget = b
if consistency_errs:
consistency_failed.append((step, "; ".join(consistency_errs)))
total_steps = len(history)
schema_ok = total_steps - len(schema_failed)
consistency_ok = total_steps - len(consistency_failed)
summary = _kpi_grid(
[
("Schema valid", _fmt_pct(schema_ok, total_steps)),
("Consistency OK", _fmt_pct(consistency_ok, total_steps)),
("Violations", str(len(schema_failed) + len(consistency_failed))),
]
)
failures: List[Tuple[int, str, str]] = [
(s, "schema", msg) for s, msg in schema_failed
] + [(s, "consistency", msg) for s, msg in consistency_failed]
failures.sort(key=lambda x: (x[0], x[1]))
if not failures:
fail_html = "<div style='color:#16a34a;margin-top:6px'>No violations detected.</div>"
else:
max_rows = 12
rows = []
head = _tr([
_th("Step"),
_th("Type"),
_th("Reason"),
], style=_HEADER_ROW_STYLE)
for s, kind, msg in failures[:max_rows]:
rows.append(
_tr(
[
_td(str(s), _CELL_STYLE),
_td(kind, _CELL_STYLE),
_td(html.escape(msg), f"{_CELL_STYLE};text-align:left"),
]
)
)
body = "".join(rows)
extra = ""
if len(failures) > max_rows:
extra = (
f"<div style='color:#6b7280;margin-top:6px'>"
f"Showing {max_rows} of {len(failures)} violations."
f"</div>"
)
fail_html = _table(head, body) + extra
return (
"<details style='margin-top:10px'>"
"<summary style='cursor:pointer;font-weight:600;color:#111827'>Data quality (optional)</summary>"
f"<div style='margin-top:8px'>{summary}</div>"
f"<div style='margin-top:8px'>{fail_html}</div>"
"</details>"
)
except Exception as exc:
return (
"<details style='margin-top:10px'>"
"<summary style='cursor:pointer;font-weight:600;color:#111827'>Data quality (optional)</summary>"
f"<div style='color:#dc2626;margin-top:6px'>Error computing data quality: {html.escape(str(exc))}</div>"
"</details>"
)
def _step_badges(last: Optional[Dict]) -> str:
if not last:
return "<div style='color:#aaa;font-style:italic'>No steps yet.</div>"
action = str(last.get("action") or "")
if action == "shed_load":
return (
"<div style='display:flex;gap:8px;align-items:center;margin-top:6px'>"
"<span style='padding:2px 8px;border-radius:999px;background:#6b7280;color:white;font-size:12px;font-weight:600'>SHED LOAD</span>"
"<span style='padding:2px 8px;border-radius:999px;background:#6b7280;color:white;font-size:12px;font-weight:600'>SLA N/A</span>"
"</div>"
)
succeeded = bool(last.get("succeeded", False))
latency_ms = float(last.get("latency_ms", 0.0) or 0.0)
sla_ms = float(last.get("sla_ceiling_ms", 500.0) or 500.0)
breach = latency_ms > sla_ms
s_color = "#16a34a" if succeeded else "#dc2626"
s_text = "SUCCESS" if succeeded else "FAILED"
b_color = "#dc2626" if breach else "#16a34a"
b_text = "SLA BREACH" if breach else "SLA OK"
return (
"<div style='display:flex;gap:8px;align-items:center;margin-top:6px'>"
f"<span style='padding:2px 8px;border-radius:999px;background:{s_color};color:white;font-size:12px;font-weight:600'>{s_text}</span>"
f"<span style='padding:2px 8px;border-radius:999px;background:{b_color};color:white;font-size:12px;font-weight:600'>{b_text}</span>"
"</div>"
)
_ACTION_COLORS = {
"route_to_a": "#d5f5e3",
"route_to_b": "#fef9e7",
"route_to_c": "#fadbd8",
"shed_load": "#f2f3f4",
}
def _normalize_action_label(action_value: Any) -> str:
raw = str(action_value or "shed_load").strip().lower()
if not raw:
return "shed_load"
matches = re.findall(r"route_to_[abc]|shed_load", raw)
if matches:
return matches[0]
return raw.split()[0]
def render_history_table_compare(history: List[Dict]) -> str:
headers = [
"Step",
"Action",
"Provider A<br>Health",
"Provider B<br>Health",
"Provider C<br>Health",
"OK",
"SLA",
"Latency<br>(ms)",
"Budget",
"Reward",
]
head = _tr([_th(h) for h in headers], style=_HEADER_ROW_STYLE)
table_style = (
"width:100%;border-collapse:collapse;table-layout:fixed;"
"font-size:12px;background:#ffffff;color:#111827"
)
colgroup = (
"<colgroup>"
"<col style='width:9%'>"
"<col style='width:16%'>"
"<col style='width:12%'>"
"<col style='width:12%'>"
"<col style='width:12%'>"
"<col style='width:6%'>"
"<col style='width:6%'>"
"<col style='width:10%'>"
"<col style='width:8%'>"
"<col style='width:9%'>"
"</colgroup>"
)
cell_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px"
step_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px"
action_style = _ACTION_CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px"
health_style = _CELL_STYLE + ";white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px"
if not history:
body = _tr(
[_td("No steps yet.", "padding:8px;color:#888;text-align:center", colspan=len(headers))]
)
else:
rows = []
for h in history:
action = _normalize_action_label(h.get("action"))
action_color = _ACTION_COLORS.get(action, "#f2f3f4")
succeeded = bool(h.get("succeeded", False))
latency_ms = float(h.get("latency_ms", 0.0) or 0.0)
sla_ms = float(h.get("sla_ceiling_ms", 500.0) or 500.0)
breach = latency_ms > sla_ms
health_a = float(h.get("health_a", 0.0) or 0.0)
health_b = float(h.get("health_b", 0.0) or 0.0)
health_c = float(h.get("health_c", 0.0) or 0.0)
if action == "shed_load":
ok_cell = "—"
sla_cell = "—"
else:
ok_cell = "✅" if succeeded else "❌"
sla_cell = "❌" if breach else "✅"
rows.append(
_tr(
[
_td(str(h.get("step", "—")), step_style),
_td(action, f"{action_style};background:{action_color}"),
_td(f"{health_a:.2f}", health_style),
_td(f"{health_b:.2f}", health_style),
_td(f"{health_c:.2f}", health_style),
_td(ok_cell, cell_style),
_td(sla_cell, cell_style),
_td(f"{latency_ms:.0f}", cell_style),
_td(f"{float(h.get('budget', 0.0) or 0.0):.2f}", cell_style),
_td(f"{float(h.get('reward', 0.0) or 0.0):+.3f}", cell_style),
]
)
)
body = "".join(rows)
return f"<table style='{table_style}'>{colgroup}<thead>{head}</thead><tbody>{body}</tbody></table>"
def _kpi_grid(items: List[Tuple[str, str]]) -> str:
cards = []
for label, value in items:
cards.append(
"<div class='kpi-card'>"
f"<div class='kpi-label'>{html.escape(str(label))}</div>"
f"<div class='kpi-value'>{html.escape(str(value))}</div>"
"</div>"
)
return "<div class='kpi-grid'>" + "".join(cards) + "</div>"
def render_incident_timeline(scenario_name: str) -> str:
config = _task_config(scenario_name)
start_raw = getattr(config, "degradation_start_step", 999)
start = 999 if start_raw is None else int(start_raw)
target = str(getattr(config, "degradation_target", "A") or "A")
secondary_start = getattr(config, "secondary_degradation_start_step", 999)
secondary_target = str(getattr(config, "secondary_degradation_target", "") or "")
items: List[str] = []
if start < 999:
items.append(f"<div><b>Step {start}</b>: degradation starts for Provider {target}</div>")
if secondary_target and secondary_start is not None and int(secondary_start) < 999:
items.append(
f"<div><b>Step {int(secondary_start)}</b>: degradation starts for Provider {secondary_target}</div>"
)
if not items:
return "<div style='color:#aaa;font-style:italic'>No configured incidents for this scenario.</div>"
return "<div style='display:flex;flex-direction:column;gap:6px'>" + "".join(items) + "</div>"
def _policy_label(name: Optional[str], fallback: str) -> str:
text = str(name or "").strip()
return text if text else fallback
def _annotation_offsets(last_left: Optional[float], last_right: Optional[float], threshold: float = 0.03) -> Tuple[int, int]:
if last_left is None or last_right is None:
return (0, 0)
if abs(last_left - last_right) < threshold:
# Keep visual ordering consistent with actual values:
# higher final score label stays above lower final score label.
if last_left >= last_right:
return (10, -10)
return (-10, 10)
return (0, 0)
def render_grader_plot(
left_hist: List[Dict],
right_hist: List[Dict],
left_name: Optional[str] = None,
right_name: Optional[str] = None,
):
try:
import plotly.graph_objects as go
except Exception:
go = None
if go is not None:
def series(hist: List[Dict]) -> List[float]:
out: List[float] = []
for i in range(1, len(hist) + 1):
out.append(float(compute_grade(hist[:i]).get("overall_score", 0.0)))
return out
y1 = series(left_hist)
y2 = series(right_hist)
x1 = list(range(1, len(y1) + 1))
x2 = list(range(1, len(y2) + 1))
color_a = "#f39c12"
color_b = "#3498db"
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
yshift_a, yshift_b = _annotation_offsets(y1[-1] if y1 else None, y2[-1] if y2 else None)
fig = go.Figure()
if y1:
fig.add_trace(
go.Scatter(
x=x1,
y=y1,
mode="lines",
name=label_a,
line=dict(color=color_a, width=3),
hovertemplate="Step %{x}<br>Benchmark Score %{y:.4%}<extra></extra>",
)
)
fig.add_annotation(
x=x1[-1],
y=y1[-1],
text=_format_percent_precise(float(y1[-1])),
showarrow=False,
xanchor="left",
xshift=8,
yshift=yshift_a,
font=dict(color=color_a, size=12),
)
if y2:
fig.add_trace(
go.Scatter(
x=x2,
y=y2,
mode="lines",
name=label_b,
line=dict(color=color_b, width=3),
hovertemplate="Step %{x}<br>Benchmark Score %{y:.4%}<extra></extra>",
)
)
fig.add_annotation(
x=x2[-1],
y=y2[-1],
text=_format_percent_precise(float(y2[-1])),
showarrow=False,
xanchor="left",
xshift=8,
yshift=yshift_b,
font=dict(color=color_b, size=12),
)
fig.update_layout(
template="plotly_white",
title=dict(text=f"{MISSION_SCORE_LABEL} over steps", x=0.0, xanchor="left"),
margin=dict(l=50, r=20, t=45, b=40),
height=320,
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0),
font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"),
)
fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False)
fig.update_yaxes(
title_text=MISSION_SCORE_LABEL,
tickformat=",.2%",
range=[0, 1],
showgrid=True,
gridcolor="rgba(17,24,39,0.08)",
zeroline=False,
)
return fig
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter
except Exception:
return None
def series(hist: List[Dict]) -> List[float]:
out: List[float] = []
for i in range(1, len(hist) + 1):
out.append(float(compute_grade(hist[:i]).get("overall_score", 0.0)))
return out
y1 = series(left_hist)
y2 = series(right_hist)
x1 = list(range(1, len(y1) + 1))
x2 = list(range(1, len(y2) + 1))
fig = plt.figure(figsize=(8, 3.2), dpi=120)
ax = fig.add_subplot(111)
color_a = "#f39c12"
color_b = "#3498db"
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
yoffset_a, yoffset_b = _annotation_offsets(y1[-1] if y1 else None, y2[-1] if y2 else None)
if y1:
ax.plot(x1, y1, label=label_a, linewidth=2.2, color=color_a)
ax.annotate(
_format_percent_precise(float(y1[-1])),
xy=(x1[-1], y1[-1]),
xytext=(6, yoffset_a),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color=color_a,
)
if y2:
ax.plot(x2, y2, label=label_b, linewidth=2.2, color=color_b)
ax.annotate(
_format_percent_precise(float(y2[-1])),
xy=(x2[-1], y2[-1]),
xytext=(6, yoffset_b),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color=color_b,
)
ax.set_ylim(0, 1)
ax.yaxis.set_major_formatter(PercentFormatter(xmax=1.0, decimals=2))
ax.set_xlabel("Step")
ax.set_ylabel(MISSION_SCORE_LABEL)
ax.set_title(f"{MISSION_SCORE_LABEL} over steps", loc="left", fontsize=11, fontweight="bold", color="#111827")
fig.patch.set_facecolor("#ffffff")
ax.set_facecolor("#ffffff")
ax.grid(True, axis="y", alpha=0.18, linewidth=0.8)
ax.grid(False, axis="x")
for spine in ("top", "right"):
ax.spines[spine].set_visible(False)
for spine in ("left", "bottom"):
ax.spines[spine].set_color("#e5e7eb")
ax.tick_params(colors="#374151", labelsize=9)
ax.legend(loc="upper left", frameon=False, fontsize=9)
fig.tight_layout()
return fig
def render_reward_plot(
left_hist: List[Dict],
right_hist: List[Dict],
left_name: Optional[str] = None,
right_name: Optional[str] = None,
):
try:
import plotly.graph_objects as go
except Exception:
go = None
def series(hist: List[Dict]) -> Tuple[List[int], List[float]]:
xs: List[int] = []
ys: List[float] = []
total = 0.0
for i, h in enumerate(hist, start=1):
total += float(h.get("reward", 0.0) or 0.0)
xs.append(i)
ys.append(total)
return xs, ys
x1, y1 = series(left_hist)
x2, y2 = series(right_hist)
if not y1 and not y2:
return None
if go is not None:
color_a = "#f39c12"
color_b = "#3498db"
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
fig = go.Figure()
if y1:
fig.add_trace(
go.Scatter(
x=x1,
y=y1,
mode="lines",
name=label_a,
line=dict(color=color_a, width=3),
hovertemplate="Step %{x}<br>Cumulative %{y:+.2f}<extra></extra>",
)
)
fig.add_annotation(
x=x1[-1],
y=y1[-1],
text=f"{y1[-1]:+.2f}",
showarrow=False,
xanchor="left",
xshift=8,
font=dict(color=color_a, size=12),
)
if y2:
fig.add_trace(
go.Scatter(
x=x2,
y=y2,
mode="lines",
name=label_b,
line=dict(color=color_b, width=3),
hovertemplate="Step %{x}<br>Cumulative %{y:+.2f}<extra></extra>",
)
)
fig.add_annotation(
x=x2[-1],
y=y2[-1],
text=f"{y2[-1]:+.2f}",
showarrow=False,
xanchor="left",
xshift=8,
font=dict(color=color_b, size=12),
)
fig.update_layout(
template="plotly_white",
title=dict(text="Cumulative reward over steps", x=0.0, xanchor="left"),
margin=dict(l=60, r=20, t=45, b=40),
height=280,
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0),
font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"),
)
fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False)
fig.update_yaxes(
title_text="Cumulative reward",
showgrid=True,
gridcolor="rgba(17,24,39,0.08)",
zeroline=False,
)
return fig
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
except Exception:
return None
fig = plt.figure(figsize=(8, 2.8), dpi=120)
ax = fig.add_subplot(111)
fig.patch.set_facecolor("#ffffff")
ax.set_facecolor("#ffffff")
ax.grid(True, axis="y", alpha=0.18, linewidth=0.8)
ax.grid(False, axis="x")
for spine in ("top", "right"):
ax.spines[spine].set_visible(False)
for spine in ("left", "bottom"):
ax.spines[spine].set_color("#e5e7eb")
ax.tick_params(colors="#374151", labelsize=9)
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
if y1:
ax.plot(x1, y1, label=label_a, linewidth=2.2, color="#f39c12")
ax.annotate(
f"{y1[-1]:+.2f}",
xy=(x1[-1], y1[-1]),
xytext=(6, 0),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color="#f39c12",
)
if y2:
ax.plot(x2, y2, label=label_b, linewidth=2.2, color="#3498db")
ax.annotate(
f"{y2[-1]:+.2f}",
xy=(x2[-1], y2[-1]),
xytext=(6, 0),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color="#3498db",
)
ax.set_xlabel("Step")
ax.set_ylabel("Cumulative reward")
ax.set_title("Cumulative reward over steps", loc="left", fontsize=11, fontweight="bold", color="#111827")
ax.legend(loc="upper left", frameon=False, fontsize=9)
fig.tight_layout()
return fig
def render_budget_consumed_plot(
left_hist: List[Dict],
right_hist: List[Dict],
left_name: Optional[str] = None,
right_name: Optional[str] = None,
):
try:
import plotly.graph_objects as go
except Exception:
go = None
if go is not None:
def series(hist: List[Dict]) -> List[float]:
if not hist:
return []
initial = float(hist[0].get("initial_budget", 0.0) or 0.0)
consumed: List[float] = []
total = 0.0
for h in hist:
total += float(h.get("cost", 0.0) or 0.0)
if initial > 0:
consumed.append(min(initial, total))
else:
consumed.append(total)
return consumed
y1 = series(left_hist)
y2 = series(right_hist)
if not y1 and not y2:
return None
x1 = list(range(1, len(y1) + 1))
x2 = list(range(1, len(y2) + 1))
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
fig = go.Figure()
if y1:
fig.add_trace(
go.Scatter(
x=x1,
y=y1,
mode="lines",
name=label_a,
line=dict(color="#f39c12", width=3),
hovertemplate="Step %{x}<br>Consumed $%{y:,.0f}<extra></extra>",
)
)
fig.add_annotation(
x=x1[-1],
y=y1[-1],
text=f"${y1[-1]:,.0f}",
showarrow=False,
xanchor="left",
xshift=8,
font=dict(color="#f39c12", size=12),
)
if y2:
fig.add_trace(
go.Scatter(
x=x2,
y=y2,
mode="lines",
name=label_b,
line=dict(color="#3498db", width=3),
hovertemplate="Step %{x}<br>Consumed $%{y:,.0f}<extra></extra>",
)
)
fig.add_annotation(
x=x2[-1],
y=y2[-1],
text=f"${y2[-1]:,.0f}",
showarrow=False,
xanchor="left",
xshift=8,
font=dict(color="#3498db", size=12),
)
fig.update_layout(
template="plotly_white",
title=dict(text="Budget consumed over steps", x=0.0, xanchor="left"),
margin=dict(l=60, r=20, t=45, b=40),
height=280,
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.0),
font=dict(family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial", size=12, color="#111827"),
)
fig.update_xaxes(title_text="Step", showgrid=False, zeroline=False)
fig.update_yaxes(
title_text="Budget consumed ($)",
tickprefix="$",
separatethousands=True,
showgrid=True,
gridcolor="rgba(17,24,39,0.08)",
zeroline=False,
)
return fig
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.ticker import StrMethodFormatter
except Exception:
return None
def series(hist: List[Dict]) -> List[float]:
if not hist:
return []
initial = float(hist[0].get("initial_budget", 0.0) or 0.0)
consumed: List[float] = []
total = 0.0
for h in hist:
total += float(h.get("cost", 0.0) or 0.0)
if initial > 0:
consumed.append(min(initial, total))
else:
consumed.append(total)
return consumed
y1 = series(left_hist)
y2 = series(right_hist)
if not y1 and not y2:
return None
x1 = list(range(1, len(y1) + 1))
x2 = list(range(1, len(y2) + 1))
fig = plt.figure(figsize=(8, 2.8), dpi=120)
ax = fig.add_subplot(111)
fig.patch.set_facecolor("#ffffff")
ax.set_facecolor("#ffffff")
label_a = _policy_label(left_name, "Policy A")
label_b = _policy_label(right_name, "Policy B")
if y1:
ax.plot(x1, y1, label=label_a, linewidth=2.2, color="#f39c12")
ax.fill_between(x1, y1, alpha=0.10, color="#f39c12")
ax.annotate(
f"${y1[-1]:,.0f}",
xy=(x1[-1], y1[-1]),
xytext=(6, 0),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color="#f39c12",
)
if y2:
ax.plot(x2, y2, label=label_b, linewidth=2.2, color="#3498db")
ax.fill_between(x2, y2, alpha=0.08, color="#3498db")
ax.annotate(
f"${y2[-1]:,.0f}",
xy=(x2[-1], y2[-1]),
xytext=(6, 0),
textcoords="offset points",
ha="left",
va="center",
fontsize=9,
fontweight=600,
color="#3498db",
)
ax.set_xlabel("Step")
ax.set_ylabel("Budget consumed ($)")
ax.set_title("Budget consumed over steps", loc="left", fontsize=11, fontweight="bold", color="#111827")
ax.yaxis.set_major_formatter(StrMethodFormatter("${x:,.0f}"))
ax.grid(True, axis="y", alpha=0.18, linewidth=0.8)
ax.grid(False, axis="x")
for spine in ("top", "right"):
ax.spines[spine].set_visible(False)
for spine in ("left", "bottom"):
ax.spines[spine].set_color("#e5e7eb")
ax.tick_params(colors="#374151", labelsize=9)
ax.legend(loc="upper left", frameon=False, fontsize=9)
fig.tight_layout()
return fig
def render_history_table(history: List[Dict]) -> str:
headers = ["Step", "Action", "Health A", "Health B", "Health C", "Budget", "Reward"]
head = _tr([_th(h) for h in headers], style=_HEADER_ROW_STYLE)
if not history:
body = _tr([
_td("No steps yet.", "padding:8px;color:#888;text-align:center", colspan=len(headers))
])
else:
rows = []
for h in history:
action = h["action"]
action_color = _ACTION_COLORS.get(action, "#f2f3f4")
rows.append(
_tr(
[
_td(str(h["step"]), _CELL_STYLE),
_td(action, f"{_ACTION_CELL_STYLE};background:{action_color}"),
_td(f"{h['health_a']:.2f}", _CELL_STYLE),
_td(f"{h['health_b']:.2f}", _CELL_STYLE),
_td(f"{h['health_c']:.2f}", _CELL_STYLE),
_td(f"{h['budget']:.2f}", _CELL_STYLE),
_td(f"{h['reward']:+.3f}", _CELL_STYLE),
]
)
)
body = "".join(rows)
return _table(head, body)
def render_side_panel(side: Dict, run: Dict, scenario_name: str) -> Tuple[str, str, str, str, str, str, str, str]:
scenario_cfg = _task_config(scenario_name)
global_step = int(run.get("step", 0) or 0)
common = _common_provider_health(scenario_cfg, global_step)
obs = side.get("obs", {}) or {}
history = side.get("history", []) or []
last = history[-1] if history else None
grade = compute_grade(history) if history else {}
adaptation = grade.get("adaptation_score") if history else None
latency = float(last.get("latency_ms", 0.0) or 0.0) if last else None
last_action = str(last.get("action")) if last else "—"
budget_val = float(obs.get("budget_remaining", 0.0) or 0.0)
reward_val = float(last.get("reward", 0.0) or 0.0) if last else None
kpis = _kpi_grid(
[
("Step", f"{global_step} / {MAX_STEPS}"),
("Last action", last_action),
("Latency (ms)", (f"{latency:.0f}" if latency is not None else "—")),
("Budget remaining", f"{budget_val:.2f}"),
("Reward", ((f"{reward_val:+.3f}") if reward_val is not None else "—")),
("Adaptation", ((f"{float(adaptation):.3f}") if adaptation is not None else "—")),
]
)
metrics = _summary_metrics(history)
summary = _kpi_grid(
[
("Failed %", f"{metrics['failed_pct']:.1f}%"),
("SLA breach %", f"{metrics['sla_breach_pct']:.1f}%"),
("Avg latency (ms)", f"{metrics['avg_latency_ms']:.1f}"),
]
)
summary = summary + render_data_quality_panel(history)
grade_html = _GRADER_PENDING()
if side.get("done") and history:
grade_html = render_grader(grade)
return (
str(side.get("status", "")),
render_common_providers(common) if global_step > 0 else _PROVIDER_EMPTY(),
render_budget(obs) if obs else "",
kpis,
_step_badges(last),
summary,
render_history_table_compare(history),
grade_html,
)