1"""Private monitoring dashboard: static HTML generated from the store (progress, costs, errors, node utilisation, validated outcomes).2Usage: python -m wp42.dashboard [--out dashboard/index.html] [--serve PORT]"""3from __future__ import annotations4import argparse, html, json, os5from .config import DB_PATH, REPO, NODES, STUDY_ENVELOPE_USD6from .store import Store7from .contracts import utcnow89CSS = "body{font-family:-apple-system,Helvetica,Arial;margin:1.5rem;color:#1d1d1f;background:#fafafa}table{border-collapse:collapse;margin:.5rem 0 1.5rem}td,th{border:1px solid #ddd;padding:3px 8px;font-size:13px;text-align:left}th{background:#f0f0f0}.ok{color:#1a7f37}.bad{color:#b42318}.muted{color:#777}h2{margin-top:2rem}"1011def render(store: Store) -> str:12 runs = store.q("SELECT * FROM runs ORDER BY order_index"); led = store.ledger_total()13 by = {}14 for r in runs: by.setdefault((r["arm"], r["status"]), 0); by[(r["arm"], r["status"])] += 115 arms = sorted({r["arm"] for r in runs}); statuses = sorted({r["status"] for r in runs})16 t1 = "<table><tr><th>arm</th>" + "".join(f"<th>{s}</th>" for s in statuses) + "</tr>" + "".join("<tr><td>" + a + "</td>" + "".join(f"<td>{by.get((a, s), 0)}</td>" for s in statuses) + "</tr>" for a in arms) + "</table>"17 met = {m["run_id"]: json.loads(m["metrics"]) for m in store.q("SELECT * FROM metrics")}18 rows = []19 for r in runs:20 m = met.get(r["run_id"], {}); q = m.get("quality"); s = (m.get("det_completion") and not m.get("critical_error") and q is not None and q >= 70)21 rows.append(f"<tr><td>{r['order_index']}</td><td>{r['run_id']}</td><td>{r['arm']}</td><td>{r['node'] or ''}</td><td>{r['status']}</td><td>{r['result_subtype'] or ''}</td>"22 f"<td>{'' if r['wall_s'] is None else int(r['wall_s'])}</td><td>{'' if r['cost_usd'] is None else round((r['cost_usd'] or 0) + (r['recovery_cost_usd'] or 0), 3)}</td>"23 f"<td>{m.get('tool_calls', '')}</td><td class='{'ok' if m.get('det_completion') else 'bad'}'>{m.get('det_completion', '')}</td><td>{m.get('n_critical_violations', '')}</td>"24 f"<td>{'' if q is None else round(q, 1)}</td><td class='{'ok' if s else ''}'>{'ACCEPTED' if s else ''}</td><td>{'rec' if r['recovery_attempted'] else ''}{'✓' if r['recovery_success'] else ''}</td><td class='muted'>{html.escape((r['notes'] or '')[:80])}</td></tr>")25 t2 = "<table><tr><th>#</th><th>run</th><th>arm</th><th>node</th><th>status</th><th>result</th><th>wall s</th><th>cost USD</th><th>tools</th><th>det</th><th>viol</th><th>quality</th><th>S</th><th>A4</th><th>notes</th></tr>" + "".join(rows) + "</table>"26 util = "".join(f"<li>{n}: {sum(1 for r in runs if r['node'] == n and r['status'] in ('running', 'preparing', 'recovering'))}/{c['slots']} active, {sum(1 for r in runs if r['node'] == n)} total</li>" for n, c in NODES.items())27 errs = [r for r in runs if r["status"] == "infra_error" or (r["result_subtype"] not in (None, "success"))]28 t3 = "<ul>" + "".join(f"<li>{r['run_id']}: {r['status']} / {r['result_subtype']} — {html.escape((r['notes'] or '')[:160])}</li>" for r in errs) + "</ul>"29 acc = {}30 for r in runs:31 m = met.get(r["run_id"])32 if not m: continue33 q = m.get("quality"); s = bool(m.get("det_completion") and not m.get("critical_error") and q is not None and q >= 70)34 a = acc.setdefault(r["arm"], {"n": 0, "S": 0, "D": 0, "E": 0, "cost": 0.0}); a["n"] += 1; a["S"] += s; a["D"] += bool(m.get("det_completion")); a["E"] += bool(m.get("critical_error")); a["cost"] += m.get("total_cost_usd") or 035 t4 = "<table><tr><th>arm</th><th>n evaluated</th><th>accepted S</th><th>det D</th><th>critical E</th><th>mean cost</th><th>cost per accepted</th></tr>" + "".join(36 f"<tr><td>{a}</td><td>{v['n']}</td><td>{v['S']} ({v['S']/v['n']:.0%})</td><td>{v['D']}</td><td>{v['E']}</td><td>{v['cost']/v['n']:.2f}</td><td>{(v['cost']/v['S']) if v['S'] else float('nan'):.2f}</td></tr>" for a, v in sorted(acc.items())) + "</table>"37 return f"<!doctype html><html><head><meta charset='utf-8'><meta http-equiv='refresh' content='60'><title>WP42 lab</title><style>{CSS}</style></head><body><h1>WP42 — Autonomous White-Collar Worker laboratory</h1><p class='muted'>generated {utcnow()} · ledger <b>{led:.2f} USD</b> of {STUDY_ENVELOPE_USD:.0f} USD envelope · {len(runs)} runs</p><h2>Progress by arm</h2>{t1}<h2>Validated outcomes by arm (S = det ∧ no violation ∧ quality ≥ 70)</h2>{t4}<h2>Node utilisation</h2><ul>{util}</ul><h2>Errors</h2>{t3 if errs else '<p class=muted>none</p>'}<h2>Runs</h2>{t2}</body></html>"3839def main():40 ap = argparse.ArgumentParser(); ap.add_argument("--out", default=os.path.join(REPO, "dashboard", "index.html")); ap.add_argument("--serve", type=int, default=0)41 a = ap.parse_args(); store = Store(DB_PATH)42 os.makedirs(os.path.dirname(a.out), exist_ok=True); open(a.out, "w").write(render(store)); print("wrote", a.out)43 if a.serve:44 from http.server import HTTPServer, BaseHTTPRequestHandler45 class H(BaseHTTPRequestHandler):46 def do_GET(self):47 body = render(Store(DB_PATH)).encode(); self.send_response(200); self.send_header("Content-Type", "text/html; charset=utf-8"); self.end_headers(); self.wfile.write(body)48 def log_message(self, *a): pass49 print(f"serving on http://127.0.0.1:{a.serve}"); HTTPServer(("127.0.0.1", a.serve), H).serve_forever()5051if __name__ == "__main__":52 main()53