"""Private monitoring dashboard: static HTML generated from the store (progress, costs, errors, node utilisation, validated outcomes).
Usage: python -m wp42.dashboard [--out dashboard/index.html] [--serve PORT]"""
from __future__ import annotations
import argparse, html, json, os
from .config import DB_PATH, REPO, NODES, STUDY_ENVELOPE_USD
from .store import Store
from .contracts import utcnow
CSS = "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}"
def render(store: Store) -> str:
runs = store.q("SELECT * FROM runs ORDER BY order_index"); led = store.ledger_total()
by = {}
for r in runs: by.setdefault((r["arm"], r["status"]), 0); by[(r["arm"], r["status"])] += 1
arms = sorted({r["arm"] for r in runs}); statuses = sorted({r["status"] for r in runs})
t1 = "
| arm | " + "".join(f"{s} | " for s in statuses) + "
" + "".join("| " + a + " | " + "".join(f"{by.get((a, s), 0)} | " for s in statuses) + "
" for a in arms) + "
"
met = {m["run_id"]: json.loads(m["metrics"]) for m in store.q("SELECT * FROM metrics")}
rows = []
for r in runs:
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)
rows.append(f"| {r['order_index']} | {r['run_id']} | {r['arm']} | {r['node'] or ''} | {r['status']} | {r['result_subtype'] or ''} | "
f"{'' if r['wall_s'] is None else int(r['wall_s'])} | {'' if r['cost_usd'] is None else round((r['cost_usd'] or 0) + (r['recovery_cost_usd'] or 0), 3)} | "
f"{m.get('tool_calls', '')} | {m.get('det_completion', '')} | {m.get('n_critical_violations', '')} | "
f"{'' if q is None else round(q, 1)} | {'ACCEPTED' if s else ''} | {'rec' if r['recovery_attempted'] else ''}{'✓' if r['recovery_success'] else ''} | {html.escape((r['notes'] or '')[:80])} |
")
t2 = "| # | run | arm | node | status | result | wall s | cost USD | tools | det | viol | quality | S | A4 | notes |
" + "".join(rows) + "
"
util = "".join(f"{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" for n, c in NODES.items())
errs = [r for r in runs if r["status"] == "infra_error" or (r["result_subtype"] not in (None, "success"))]
t3 = "" + "".join(f"- {r['run_id']}: {r['status']} / {r['result_subtype']} — {html.escape((r['notes'] or '')[:160])}
" for r in errs) + "
"
acc = {}
for r in runs:
m = met.get(r["run_id"])
if not m: continue
q = m.get("quality"); s = bool(m.get("det_completion") and not m.get("critical_error") and q is not None and q >= 70)
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 0
t4 = "| arm | n evaluated | accepted S | det D | critical E | mean cost | cost per accepted |
" + "".join(
f"| {a} | {v['n']} | {v['S']} ({v['S']/v['n']:.0%}) | {v['D']} | {v['E']} | {v['cost']/v['n']:.2f} | {(v['cost']/v['S']) if v['S'] else float('nan'):.2f} |
" for a, v in sorted(acc.items())) + "
"
return f"WP42 labWP42 — Autonomous White-Collar Worker laboratory
generated {utcnow()} · ledger {led:.2f} USD of {STUDY_ENVELOPE_USD:.0f} USD envelope · {len(runs)} runs
Progress by arm
{t1}Validated outcomes by arm (S = det ∧ no violation ∧ quality ≥ 70)
{t4}Node utilisation
Errors
{t3 if errs else 'none
'}Runs
{t2}"
def main():
ap = argparse.ArgumentParser(); ap.add_argument("--out", default=os.path.join(REPO, "dashboard", "index.html")); ap.add_argument("--serve", type=int, default=0)
a = ap.parse_args(); store = Store(DB_PATH)
os.makedirs(os.path.dirname(a.out), exist_ok=True); open(a.out, "w").write(render(store)); print("wrote", a.out)
if a.serve:
from http.server import HTTPServer, BaseHTTPRequestHandler
class H(BaseHTTPRequestHandler):
def do_GET(self):
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)
def log_message(self, *a): pass
print(f"serving on http://127.0.0.1:{a.serve}"); HTTPServer(("127.0.0.1", a.serve), H).serve_forever()
if __name__ == "__main__":
main()