Initial benchmark suite: 8 graded models + cyberpunk dashboard generator
- prompts/: LFU cache exam + 5-pillar grading rubric - outputs/: 8 model .py outputs (local + cloud baseline) - data/benchmark_history.json: graded results (scores, metrics, bugs, patches) - generate_dashboard.py: builds dashboard.html + pages/*.html from JSON - Dockerfile + DEPLOY.md: Gitea→Coolify deploy (build-step, nginx static) - .gitignore: generated HTML excluded (built on deploy) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generator: builds dashboard.html + pages/<slug>..html from data/benchmark_history.json.
|
||||
Re-run after each grading batch to regenerate everything.
|
||||
Cyberpunk-terminal aesthetic. Pure stdlib + Chart.js via CDN.
|
||||
"""
|
||||
import json, html, os, sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DATA = os.path.join(HERE, "data", "benchmark_history.json")
|
||||
OUT_DASH = os.path.join(HERE, "dashboard.html")
|
||||
PAGES_DIR = os.path.join(HERE, "pages")
|
||||
|
||||
PILLARS = ["complexity", "concurrency", "isolation", "memory_edge_cases", "test_integrity"]
|
||||
PILLAR_LABELS = {
|
||||
"complexity": "Complexity (O(1))",
|
||||
"concurrency": "Concurrency / Races",
|
||||
"isolation": "Tx Isolation",
|
||||
"memory_edge_cases": "Memory & Edges",
|
||||
"test_integrity": "Test Integrity",
|
||||
}
|
||||
|
||||
# ---- cyberpunk palette ----
|
||||
NEON_CYAN = "#00ffc8"
|
||||
NEON_MAG = "#ff2bd6"
|
||||
NEON_LIME = "#b6ff3a"
|
||||
NEON_AMBER= "#ffb000"
|
||||
NEON_RED = "#ff3b5c"
|
||||
BG = "#0a0a0f"
|
||||
PANEL = "#101018"
|
||||
INK = "#d7e0e6" # body text
|
||||
INK_DIM = "#7a8590" # muted
|
||||
|
||||
def verdict_meta(v):
|
||||
return {
|
||||
"Production Ready": (NEON_LIME, "PROD"),
|
||||
"Minor Logic Flaws": (NEON_AMBER, "FLAWS"),
|
||||
"Critical Bugs": (NEON_RED, "CRIT"),
|
||||
"Broken / Unusable": (NEON_RED, "DEAD"),
|
||||
}.get(v, (INK_DIM, "???"))
|
||||
|
||||
def esc(s):
|
||||
return html.escape(str(s)) if s is not None else ""
|
||||
|
||||
def speed_str(m):
|
||||
if m.get("tok_sec") is None:
|
||||
return "N/A"
|
||||
return f"{m['tok_sec']:.1f}"
|
||||
|
||||
# series colors for charts (cycles through neon accents)
|
||||
SERIES = [NEON_CYAN, NEON_MAG, NEON_LIME, NEON_AMBER, "#5b8cff", "#c084fc", "#34d399", "#f472b6"]
|
||||
|
||||
# shared <head> CSS — embedded so each page is standalone
|
||||
def shared_css():
|
||||
return """
|
||||
:root{
|
||||
--bg:#0a0a0f; --panel:#101018; --panel2:#14141f; --ink:#d7e0e6; --dim:#7a8590;
|
||||
--cyan:#00ffc8; --mag:#ff2bd6; --lime:#b6ff3a; --amber:#ffb000; --red:#ff3b5c; --blue:#5b8cff;
|
||||
--grid: rgba(0,255,200,0.04);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0}
|
||||
body{
|
||||
background:var(--bg); color:var(--ink);
|
||||
font-family:'Fira Sans',system-ui,-apple-system,sans-serif;
|
||||
line-height:1.6; min-height:100vh;
|
||||
/* scanline + grid texture overlay */
|
||||
background-image:
|
||||
repeating-linear-gradient(0deg, rgba(0,255,200,0.025) 0px, rgba(0,255,200,0.025) 1px, transparent 1px, transparent 3px),
|
||||
linear-gradient(var(--grid) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--grid) 1px, transparent 1px),
|
||||
radial-gradient(1200px 600px at 70% -10%, rgba(91,139,255,0.10), transparent 60%),
|
||||
radial-gradient(900px 500px at 10% 110%, rgba(255,43,214,0.08), transparent 60%);
|
||||
background-size: 3px 3px, 44px 44px, 44px 44px, 100% 100%, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
mono,.mono,h1,h2,h3,.hud,th,.code,.badge,.stat-num,.lead-model,.verdict-chip{font-family:'Fira Code',ui-monospace,'SF Mono',Menlo,monospace}
|
||||
a{color:var(--cyan);text-decoration:none}
|
||||
a:hover{text-shadow:0 0 8px var(--cyan)}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:28px 20px 80px}
|
||||
header.hud-bar{
|
||||
border:1px solid rgba(0,255,200,0.25);
|
||||
background:linear-gradient(180deg,rgba(0,255,200,0.06),rgba(0,0,0,0));
|
||||
box-shadow:0 0 24px rgba(0,255,200,0.08), inset 0 0 30px rgba(0,0,0,0.5);
|
||||
border-radius:6px; padding:18px 22px; margin-bottom:24px;
|
||||
position:relative; overflow:hidden;
|
||||
}
|
||||
header.hud-bar::before{
|
||||
content:""; position:absolute; inset:0;
|
||||
background:repeating-linear-gradient(90deg, transparent 0 7px, rgba(0,255,200,0.03) 7px 8px);
|
||||
pointer-events:none;
|
||||
}
|
||||
.kicker{color:var(--cyan); letter-spacing:.32em; font-size:.72rem; text-transform:uppercase; text-shadow:0 0 10px rgba(0,255,200,0.5)}
|
||||
h1{font-size:1.5rem; margin:.3em 0 0; letter-spacing:.02em; text-shadow:0 0 16px rgba(0,255,200,0.35)}
|
||||
.subtitle{color:var(--dim); font-size:.9rem; margin-top:.35em}
|
||||
.stats{display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin:18px 0 26px}
|
||||
.stat{background:var(--panel); border:1px solid rgba(255,255,255,0.06); border-radius:6px; padding:14px 16px; position:relative; overflow:hidden}
|
||||
.stat::after{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--cyan);box-shadow:0 0 12px var(--cyan)}
|
||||
.stat .lbl{color:var(--dim);font-size:.68rem;letter-spacing:.18em;text-transform:uppercase}
|
||||
.stat .num{font-size:1.6rem;margin-top:4px;color:var(--ink)}
|
||||
.grid2{display:grid;grid-template-columns:1.1fr .9fr;gap:18px;margin-bottom:26px}
|
||||
.panel{background:var(--panel);border:1px solid rgba(255,255,255,0.07);border-radius:8px;padding:18px}
|
||||
.panel h2{font-size:.95rem;letter-spacing:.12em;text-transform:uppercase;color:var(--cyan);margin:0 0 14px;text-shadow:0 0 10px rgba(0,255,200,0.35)}
|
||||
.chart-box{position:relative;height:340px}
|
||||
@media(max-width:900px){.grid2{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,1fr)}}
|
||||
/* leaderboard */
|
||||
table{width:100%;border-collapse:collapse;font-size:.86rem}
|
||||
thead th{text-align:left;color:var(--dim);font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;border-bottom:1px solid rgba(0,255,200,0.2);padding:8px 10px}
|
||||
tbody td{padding:11px 10px;border-bottom:1px solid rgba(255,255,255,0.05);vertical-align:middle}
|
||||
tbody tr{transition:background .15s, box-shadow .15s}
|
||||
tbody tr:hover{background:rgba(0,255,200,0.05);box-shadow:inset 0 0 0 1px rgba(0,255,200,0.25)}
|
||||
.rank{color:var(--dim);width:34px}
|
||||
.rank.top{color:var(--lime);text-shadow:0 0 8px var(--lime)}
|
||||
.model-name{color:var(--ink)}
|
||||
.quant{color:var(--dim);font-size:.78rem}
|
||||
.badge{display:inline-block;padding:2px 9px;border-radius:3px;font-size:.68rem;letter-spacing:.1em;border:1px solid currentColor}
|
||||
.btn{display:inline-block;padding:5px 12px;border:1px solid var(--cyan);color:var(--cyan);border-radius:4px;font-size:.74rem;letter-spacing:.08em;cursor:pointer;transition:all .18s}
|
||||
.btn:hover{background:rgba(0,255,200,0.12);box-shadow:0 0 14px rgba(0,255,200,0.4)}
|
||||
.bar-cell{display:flex;align-items:center;gap:10px}
|
||||
.score-bar{flex:1;height:8px;background:rgba(255,255,255,0.06);border-radius:2px;overflow:hidden;min-width:70px}
|
||||
.score-bar > i{display:block;height:100%;border-radius:2px;transition:width .4s, box-shadow .2s}
|
||||
tr:hover .score-bar > i{box-shadow:0 0 12px currentColor}
|
||||
.caveat{color:var(--amber);font-size:.72rem}
|
||||
.cloud-tag{color:var(--blue);font-size:.7rem;border:1px solid rgba(91,139,255,.4);padding:1px 6px;border-radius:3px;margin-left:6px}
|
||||
footer{color:var(--dim);font-size:.74rem;margin-top:40px;border-top:1px solid rgba(255,255,255,0.06);padding-top:14px;text-align:center}
|
||||
@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}}
|
||||
"""
|
||||
|
||||
def head_html(title):
|
||||
return f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{esc(title)}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Fira+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<style>{shared_css()}</style>
|
||||
</head>
|
||||
<body><div class="wrap">
|
||||
"""
|
||||
|
||||
FOOT = """</div>
|
||||
<script>if(window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches&&window.Chart){Chart.defaults.animation=false;Chart.defaults.animations.colors=false;Chart.defaults.animations.numbers=false;}</script>
|
||||
</body></html>"""
|
||||
|
||||
def render_dashboard(data):
|
||||
models = sorted(data["models"], key=lambda m: -m["total_score"])
|
||||
n = len(models)
|
||||
avg = sum(m["total_score"] for m in models) / n if n else 0
|
||||
top = models[0] if models else None
|
||||
prod = sum(1 for m in models if m["verdict"] == "Production Ready")
|
||||
|
||||
# chart datasets (exclude cloud model from speed bars — has no tok/sec)
|
||||
local = [m for m in models if m.get("tok_sec") is not None]
|
||||
local_sorted = sorted(local, key=lambda m: -m["tok_sec"])
|
||||
|
||||
radar_models = [m for m in models if m.get("tok_sec") is not None or True][:3] # top 3 by score already
|
||||
|
||||
rows = []
|
||||
for i, m in enumerate(models, 1):
|
||||
col, chip = verdict_meta(m["verdict"])
|
||||
caveat = ""
|
||||
if m.get("speed_caveat"):
|
||||
caveat = '<div class="caveat">⚠ speed suspect</div>'
|
||||
cloud = '<span class="cloud-tag">CLOUD</span>' if m.get("format") == "cloud" else ""
|
||||
bar_color = col
|
||||
rows.append(f"""<tr>
|
||||
<td class="rank {'top' if i<=3 else ''}">#{i}</td>
|
||||
<td><div class="model-name">{esc(m['model_name'])}{cloud}</div><div class="quant">{esc(m['quant'])}</div>{caveat}</td>
|
||||
<td class="mono">{speed_str(m)} <span style="color:var(--dim);font-size:.72rem">t/s</span></td>
|
||||
<td><div class="bar-cell"><span class="mono" style="width:34px;color:{col}">{m['total_score']}</span>
|
||||
<span class="score-bar"><i style="width:{m['total_score']}%;background:{bar_color};color:{bar_color}"></i></span></div></td>
|
||||
<td><span class="badge verdict-chip" style="color:{col}">{chip}</span></td>
|
||||
<td style="color:var(--dim);font-size:.8rem">{esc(m['best_for'])[:70]}…</td>
|
||||
<td><a class="btn" href="pages/{esc(m['id'])}.html">DECODE ▸</a></td>
|
||||
</tr>""")
|
||||
|
||||
# JSON for charts
|
||||
bar_labels = json.dumps([m["model_name"].split("(")[0].strip()[:18] for m in local_sorted])
|
||||
bar_speed = json.dumps([m["tok_sec"] for m in local_sorted])
|
||||
bar_score = json.dumps([m["total_score"] for m in local_sorted])
|
||||
|
||||
radar_labels = json.dumps([PILLAR_LABELS[p] for p in PILLARS])
|
||||
radar_sets = []
|
||||
for idx, m in enumerate(radar_models):
|
||||
col = SERIES[idx % len(SERIES)]
|
||||
radar_sets.append({
|
||||
"label": m["model_name"][:24],
|
||||
"data": [m["breakdown"][p] for p in PILLARS],
|
||||
"borderColor": col,
|
||||
"backgroundColor": col + "22",
|
||||
})
|
||||
radar_json = json.dumps(radar_sets)
|
||||
|
||||
stats = f"""
|
||||
<div class="stat"><div class="lbl">Models Tested</div><div class="num mono">{n}</div></div>
|
||||
<div class="stat"><div class="lbl">Top Score</div><div class="num mono" style="color:var(--lime)">{top['total_score'] if top else '—'}</div></div>
|
||||
<div class="stat"><div class="lbl">Average</div><div class="num mono" style="color:var(--amber)">{avg:.1f}</div></div>
|
||||
<div class="stat"><div class="lbl">Prod-Ready</div><div class="num mono" style="color:var(--cyan)">{prod}/{n}</div></div>
|
||||
"""
|
||||
top_name = esc(top["model_name"]) if top else "—"
|
||||
|
||||
body = f"""
|
||||
{head_html("LLM Benchmark Suite")}
|
||||
<header class="hud-bar">
|
||||
<div class="kicker">▚ LOCAL LLM BENCHMARK SUITE // LFU CACHE & ACID AUDIT</div>
|
||||
<h1>// BENCHMARK_RESULTS <span style="color:var(--mag)">.json</span></h1>
|
||||
<div class="subtitle">{n} models graded on a strict 5-pillar / 100-pt rubric · O(1) LFU + ACID transactions · M3 Max · LM Studio
|
||||
· <span style="color:var(--cyan)">TOP: {top_name}</span></div>
|
||||
</header>
|
||||
{stats}
|
||||
<div class="grid2">
|
||||
<div class="panel">
|
||||
<h2>▮ Score vs Throughput (tok/sec)</h2>
|
||||
<div class="chart-box"><canvas id="bar"></canvas></div>
|
||||
<div style="color:var(--dim);font-size:.72rem;margin-top:8px">Local models only — cloud baseline (DeepSeek) excluded from speed axis. Gemma 4 bars flagged ⚠ (GPU-offload suspect).</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>▮ 5-Pillar Radar — Top 3</h2>
|
||||
<div class="chart-box"><canvas id="radar"></canvas></div>
|
||||
<div style="color:var(--dim);font-size:.72rem;margin-top:8px">Each pillar scored 0–20. Outer = stronger.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel" style="margin-bottom:26px">
|
||||
<h2>▮ LEADERBOARD</h2>
|
||||
<div style="overflow-x:auto">
|
||||
<table>
|
||||
<thead><tr><th>#</th><th>Model</th><th>Speed</th><th>Score</th><th>Verdict</th><th>Best For</th><th></th></tr></thead>
|
||||
<tbody>{''.join(rows)}</tbody>
|
||||
</table></div>
|
||||
</div>
|
||||
<footer>Generated from <span class="mono">data/benchmark_history.json</span> · re-run <span class="mono">generate_dashboard.py</span> to refresh · cyberpunk-terminal UI</footer>
|
||||
<script>
|
||||
const NEON={{cyan:'#00ffc8',mag:'#ff2bd6',lime:'#b6ff3a',amber:'#ffb000',red:'#ff3b5c',blue:'#5b8cff'}};
|
||||
new Chart(document.getElementById('bar'),{{
|
||||
type:'bar',
|
||||
data:{{labels:{bar_labels},
|
||||
datasets:[
|
||||
{{label:'Score /100',data:{bar_score},backgroundColor:'rgba(0,255,200,0.85)',borderColor:NEON.cyan,borderRadius:3,yAxisID:'y'}},
|
||||
{{label:'tok/sec',data:{bar_speed},backgroundColor:'rgba(255,43,214,0.7)',borderColor:NEON.mag,borderRadius:3,yAxisID:'y1'}}
|
||||
]}},
|
||||
options:{{maintainAspectRatio:false,responsive:true,
|
||||
plugins:{{legend:{{labels:{{color:'#d7e0e6',font:{{family:'Fira Code'}}}}}}}},
|
||||
scales:{{
|
||||
x:{{ticks:{{color:'#7a8590',font:{{family:'Fira Code',size:10}}}},grid:{{color:'rgba(255,255,255,0.05)'}}}},
|
||||
y:{{position:'left',max:100,title:{{display:true,text:'Score',color:'#00ffc8'}},ticks:{{color:'#7a8590'}},grid:{{color:'rgba(255,255,255,0.05)'}}}},
|
||||
y1:{{position:'right',title:{{display:true,text:'tok/sec',color:'#ff2bd6'}},grid:{{drawOnChartArea:false}},ticks:{{color:'#7a8590'}}}}
|
||||
}}}}
|
||||
}});
|
||||
new Chart(document.getElementById('radar'),{{
|
||||
type:'radar',
|
||||
data:{{labels:{radar_labels},datasets:{radar_json}}},
|
||||
options:{{maintainAspectRatio:false,responsive:true,
|
||||
plugins:{{legend:{{labels:{{color:'#d7e0e6',font:{{family:'Fira Code',size:11}}}}}}}},
|
||||
scales:{{r:{{min:0,max:20,
|
||||
angleLines:{{color:'rgba(0,255,200,0.15)'}},
|
||||
grid:{{color:'rgba(0,255,200,0.12)'}},
|
||||
pointLabels:{{color:'#d7e0e6',font:{{family:'Fira Code',size:10}}}},
|
||||
ticks:{{color:'#7a8590',backdropColor:'transparent',stepSize:5}}
|
||||
}}}}
|
||||
}}
|
||||
}});
|
||||
</script>
|
||||
{FOOT}"""
|
||||
return body
|
||||
|
||||
def render_detail(m, data):
|
||||
col, chip = verdict_meta(m["verdict"])
|
||||
# derive "what went right" from high pillars, "wrong" from low + critical_bugs
|
||||
bd = m["breakdown"]
|
||||
ranked = sorted(PILLARS, key=lambda p: -bd[p])
|
||||
rights = [f"{PILLAR_LABELS[p]} ({bd[p]}/20)" for p in ranked if bd[p] >= 16]
|
||||
wrongs_pillars = [f"{PILLAR_LABELS[p]} ({bd[p]}/20)" for p in ranked if bd[p] <= 13]
|
||||
|
||||
pillar_bars = ""
|
||||
for p in PILLARS:
|
||||
v = bd[p]
|
||||
c = NEON_LIME if v >= 17 else (NEON_AMBER if v >= 13 else NEON_RED)
|
||||
pillar_bars += f"""
|
||||
<div class="prow">
|
||||
<div class="plabel">{PILLAR_LABELS[p]}</div>
|
||||
<div class="pbar"><i style="width:{v/20*100}%;background:{c};color:{c}"></i></div>
|
||||
<div class="pval mono" style="color:{c}">{v}<span style="color:var(--dim)">/20</span></div>
|
||||
</div>"""
|
||||
|
||||
bugs_html = "".join(f"<li>{esc(b)}</li>" for b in m.get("critical_bugs", []))
|
||||
patch = m.get("patch_code", "")
|
||||
metrics_block = ""
|
||||
if m.get("tok_sec") is not None:
|
||||
metrics_block = f"""
|
||||
<div class="mini"><span class="mlbl">tok/sec</span><span class="mval mono" style="color:var(--mag)">{m['tok_sec']:.2f}</span></div>
|
||||
<div class="mini"><span class="mlbl">tokens</span><span class="mval mono">{m.get('total_tokens') or '—'}</span></div>
|
||||
<div class="mini"><span class="mlbl">TTFT</span><span class="mval mono">{m.get('ttft_sec'):.2f}s</span> </div>"""
|
||||
else:
|
||||
metrics_block = '<div class="mini" style="grid-column:1/-1"><span class="mlbl">runtime</span><span class="mval mono" style="color:var(--blue)">CLOUD — not measured</span></div>'
|
||||
|
||||
caveat_html = f'<div class="caveat-box">⚠ SPEED CAVEAT: {esc(m["speed_caveat"])}</div>' if m.get("speed_caveat") else ""
|
||||
tests_html = '<span class="ok">PASS</span>' if m.get("tests_pass") else '<span class="bad">CRASH</span>'
|
||||
cloud_tag = '<span class="cloud-tag">CLOUD BASELINE</span>' if m.get("format") == "cloud" else ""
|
||||
|
||||
rights_html = "".join(f"<li>{esc(r)}</li>" for r in rights) or "<li class='dim'>No pillar reached 16+ — no standout strengths.</li>"
|
||||
wrongs_html = "".join(f"<li>{esc(w)}</li>" for w in wrongs_pillars) or "<li class='dim'>No pillar fell below 14 — solid across the board.</li>"
|
||||
|
||||
extra_css = """
|
||||
.mini-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:14px 0}
|
||||
.mini{background:var(--panel2);border:1px solid rgba(255,255,255,0.06);border-radius:5px;padding:10px 12px}
|
||||
.mlbl{display:block;color:var(--dim);font-size:.62rem;letter-spacing:.14em;text-transform:uppercase}
|
||||
.mval{display:block;font-size:1.15rem;margin-top:3px}
|
||||
.caveat-box{background:rgba(255,176,0,0.08);border:1px solid rgba(255,176,0,0.4);color:var(--amber);padding:10px 14px;border-radius:5px;font-size:.82rem;margin:12px 0}
|
||||
.pillar-list{margin-top:8px}
|
||||
.prow{display:grid;grid-template-columns:170px 1fr 56px;align-items:center;gap:12px;margin:9px 0}
|
||||
.plabel{color:var(--ink);font-size:.84rem}
|
||||
.pbar{height:9px;background:rgba(255,255,255,0.06);border-radius:2px;overflow:hidden}
|
||||
.pbar > i{display:block;height:100%;border-radius:2px}
|
||||
.pval{text-align:right;font-size:.92rem}
|
||||
.ok{color:var(--lime);border:1px solid var(--lime);padding:1px 8px;border-radius:3px;font-size:.7rem}
|
||||
.bad{color:var(--red);border:1px solid var(--red);padding:1px 8px;border-radius:3px;font-size:.7rem}
|
||||
.section{background:var(--panel);border:1px solid rgba(255,255,255,0.07);border-radius:8px;padding:18px;margin-top:18px}
|
||||
.section h2{font-size:.9rem;letter-spacing:.12em;text-transform:uppercase;color:var(--cyan);margin:0 0 12px;text-shadow:0 0 10px rgba(0,255,200,0.35)}
|
||||
ul.clean{list-style:none;padding:0;margin:0}
|
||||
ul.clean li{padding:7px 0 7px 18px;border-bottom:1px solid rgba(255,255,255,0.05);position:relative;font-size:.88rem}
|
||||
ul.clean li::before{content:"▸";position:absolute;left:0;color:var(--cyan)}
|
||||
ul.clean li.dim{color:var(--dim)}
|
||||
ul.clean li.dim::before{color:var(--dim)}
|
||||
.bugs li::before{content:"✗";color:var(--red)}
|
||||
.rights li::before{content:"✓";color:var(--lime)}
|
||||
pre.code{background:#06060a;border:1px solid rgba(0,255,200,0.2);border-radius:6px;padding:14px;overflow-x:auto;color:#cfe;font-size:.78rem;line-height:1.5;box-shadow:inset 0 0 30px rgba(0,0,0,0.6)}
|
||||
pre.code::before{content:"// patch.py";display:block;color:var(--dim);font-size:.68rem;margin-bottom:8px}
|
||||
.back{display:inline-block;margin-bottom:18px;font-size:.8rem}
|
||||
.verdict-chip-lg{display:inline-block;padding:4px 14px;border-radius:3px;border:1px solid currentColor;font-size:.78rem;letter-spacing:.1em}
|
||||
.best-box{background:linear-gradient(135deg,rgba(0,255,200,0.08),rgba(91,139,255,0.05));border:1px solid rgba(0,255,200,0.25);border-radius:8px;padding:16px 18px;font-size:.95rem;line-height:1.6}
|
||||
@media(max-width:640px){.prow{grid-template-columns:1fr auto}.mini-grid{grid-template-columns:repeat(2,1fr)}}
|
||||
"""
|
||||
head = head_html(m["model_name"])
|
||||
# inject extra css before </style>
|
||||
head = head.replace("</style>", extra_css + "</style>")
|
||||
|
||||
return f"""
|
||||
{head}
|
||||
<a class="back" href="../dashboard.html">◂ BACK TO LEADERBOARD</a>
|
||||
<header class="hud-bar">
|
||||
<div class="kicker">▚ MODEL AUDIT // {esc(m['quant'])}</div>
|
||||
<h1>// {esc(m['model_name'])} {cloud_tag}</h1>
|
||||
<div style="margin-top:8px;display:flex;gap:14px;align-items:center;flex-wrap:wrap">
|
||||
<span class="verdict-chip-lg" style="color:{col}">{chip} — {esc(m['verdict'])}</span>
|
||||
<span class="mono" style="font-size:1.5rem;color:{col}">{m['total_score']}<span style="color:var(--dim);font-size:.8rem">/100</span></span>
|
||||
<span>tests: {tests_html}</span>
|
||||
</div>
|
||||
</header>
|
||||
{caveat_html}
|
||||
<div class="mini-grid">{metrics_block}</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>▮ PILLAR BREAKDOWN</h2>
|
||||
<div class="pillar-list">{pillar_bars}</div>
|
||||
</div>
|
||||
|
||||
<div class="grid2">
|
||||
<div class="section" style="margin-top:0">
|
||||
<h2 style="color:var(--lime);text-shadow:0 0 10px rgba(182,255,58,0.4)">✓ WENT RIGHT</h2>
|
||||
<ul class="clean rights">{rights_html}</ul>
|
||||
</div>
|
||||
<div class="section" style="margin-top:0">
|
||||
<h2 style="color:var(--red);text-shadow:0 0 10px rgba(255,59,92,0.4)">✗ WENT WRONG</h2>
|
||||
<ul class="clean wrongs">{wrongs_html}</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>▮ CRITICAL BUGS</h2>
|
||||
<ul class="clean bugs">{bugs_html or '<li class="dim">None flagged.</li>'}</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>▮ RECOMMENDED USE</h2>
|
||||
<div class="best-box">{esc(m['best_for'])}</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>▮ REFACTORED PATCH</h2>
|
||||
<pre class="code">{esc(patch)}</pre>
|
||||
</div>
|
||||
|
||||
<footer>Source: <span class="mono">{esc(m['filename'])}</span> · graded {esc(m.get('timestamp','')[:10])}</footer>
|
||||
</div>
|
||||
{FOOT}"""
|
||||
|
||||
def main():
|
||||
with open(DATA) as f:
|
||||
data = json.load(f)
|
||||
os.makedirs(PAGES_DIR, exist_ok=True)
|
||||
with open(OUT_DASH, "w") as f:
|
||||
f.write(render_dashboard(data))
|
||||
for m in data["models"]:
|
||||
with open(os.path.join(PAGES_DIR, m["id"] + ".html"), "w") as f:
|
||||
f.write(render_detail(m, data))
|
||||
print(f"OK: dashboard.html + {len(data['models'])} pages in pages/")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user