#!/usr/bin/env python3 """ Self-contained HTML report generator. Renders a single HTML file with inline styles and an inline SVG scatter plot. No external assets, no CDN calls, nothing to fetch. Safe to open directly in a browser. """ from __future__ import annotations import html from pathlib import Path from typing import Any def _esc(s: Any) -> str: return html.escape(str(s if s is not None else "")) def _scatter_svg( market_tokens: int, market_f1: float, baseline_tokens: int, baseline_f1: float, *, width: int = 520, height: int = 320, ) -> str: pad_l, pad_r, pad_t, pad_b = 70, 30, 30, 50 plot_w = width - pad_l - pad_r plot_h = height - pad_t - pad_b max_tokens = max(market_tokens, baseline_tokens, 1) # Give a little headroom so points aren't on the axis. max_tokens_axis = max_tokens * 1.15 min_tokens_axis = 0 def sx(tokens: float) -> float: frac = (tokens - min_tokens_axis) / max( max_tokens_axis - min_tokens_axis, 1 ) return pad_l + frac * plot_w def sy(f1: float) -> float: # y=0 at top of plot, y=1 at bottom -> invert return pad_t + (1.0 - max(0.0, min(1.0, f1))) * plot_h axis_color = "#555" grid_color = "#eee" market_color = "#2563eb" baseline_color = "#dc2626" parts: list[str] = [] parts.append( f'") return "".join(parts) CSS = """\ body { font-family: -apple-system, system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #1f2937; line-height: 1.55; } h1, h2, h3 { color: #111827; } h1 { border-bottom: 2px solid #2563eb; padding-bottom: .4rem; } .verdict-pass { display: inline-block; background: #dcfce7; color: #166534; padding: .3rem .8rem; border-radius: 6px; font-weight: 600; } .verdict-fail { display: inline-block; background: #fee2e2; color: #991b1b; padding: .3rem .8rem; border-radius: 6px; font-weight: 600; } table { border-collapse: collapse; margin: .8rem 0; width: 100%; } th, td { border: 1px solid #e5e7eb; padding: .4rem .6rem; text-align: left; vertical-align: top; } th { background: #f9fafb; } pre, code { background: #f3f4f6; border-radius: 4px; padding: .1rem .4rem; font-size: .9rem; } pre { padding: .8rem; white-space: pre-wrap; word-break: break-word; } .card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem 1.2rem; margin: 1rem 0; background: #fff; } .kv { display: grid; grid-template-columns: 180px 1fr; gap: .3rem .8rem; } .small { color: #6b7280; font-size: .88rem; } """ def render_report(data: dict[str, Any], out_path: Path) -> None: verdict = data.get("pareto", {}) is_pass = verdict.get("verdict") == "PASS" verdict_html = ( 'PASS — strictly northwest' if is_pass else 'FAIL — not dominating baseline' ) bids_rows: list[str] = [] for b in data.get("bids", []): conf_str = "{:.2f}".format(b.get("confidence", 0) or 0) bids_rows.append( f"
Mode: {_esc(data.get('mode', ''))}
· Question: {_esc(data.get('question_id', ''))}
· Dry-run: {_esc(data.get('dry_run', False))}
{verdict_html}
Lower-right = expensive and wrong. Upper-left = cheap and correct. Marketplace must sit strictly northwest of baseline to pass.
{_esc(data.get('question', ''))}
Gold answer: {_esc(data.get('gold_answer', ''))}
| # | Sub-question | Sub-answer |
|---|
Domain: {_esc(data.get('domain', ''))}
· Budget: {_esc(data.get('max_budget_tokens', ''))}
· Awarded to: {_esc(data.get('awarded_to', ''))}
| Agent | Est. tokens | Confidence | Approach |
|---|
{_esc(market.get('answer'))}{_esc(baseline.get('answer'))}| Agent | Domain | Runs | Correct | Tokens | Score |
|---|
Generated by benchmark/report.py.
Marketplace primitives are currently stubbed in-process — see
benchmark/marketplace.py for the migration plan to the
real 016 SynapBus MCP tools.