Skip to content

Latest commit

 

History

History
976 lines (855 loc) · 35.1 KB

File metadata and controls

976 lines (855 loc) · 35.1 KB

Benchmark Dashboard

Loading benchmark results...

<style> #benchmark-dashboard table { width: 100%; border-collapse: collapse; font-size: 0.9rem; } #benchmark-dashboard th { text-align: left; padding: 0.6rem 0.8rem; border-bottom: 2px solid var(--md-default-fg-color--lightest); white-space: nowrap; } #benchmark-dashboard td { padding: 0.5rem 0.8rem; border-bottom: 1px solid var(--md-default-fg-color--lightest); vertical-align: top; } #benchmark-dashboard tr:hover td { background: var(--md-default-fg-color--lightest); } #benchmark-dashboard .status-success { color: #22863a; } #benchmark-dashboard .status-failure { color: #cb2431; } #benchmark-dashboard .error-msg { color: var(--md-default-fg-color--light); font-style: italic; } #benchmark-dashboard .section-title { margin-top: 2.5rem; margin-bottom: 0.5rem; font-size: 1.2rem; font-weight: 600; } #benchmark-dashboard .section-explanation { font-size: 0.85rem; opacity: 0.6; margin: 0.3rem 0 1.5rem 0; font-style: italic; } #benchmark-dashboard .avg-summary { font-size: 1.05rem; margin: 0.8rem 0; line-height: 1.6; } #benchmark-dashboard .avg-summary strong { font-size: 1.1rem; } #benchmark-dashboard .footnote { font-size: 0.8rem; opacity: 0.6; font-style: italic; margin: 0.5rem 0 1.5rem 0; } /* Hero section */ #benchmark-dashboard .hero-section { text-align: center; margin: 1rem 0 2.5rem 0; } #benchmark-dashboard .hero-cards { display: flex; gap: 2rem; justify-content: center; flex-wrap: wrap; margin: 1.5rem 0; } #benchmark-dashboard .hero-card { flex: 1; text-align: center; padding: 2rem; border-radius: 12px; background: var(--md-default-bg-color--light, #f5f5f5); min-width: 240px; border: 2px solid; } #benchmark-dashboard .hero-card.factory { border-color: #4285f4; } #benchmark-dashboard .hero-card.claude-code { border-color: #ff7043; } #benchmark-dashboard .hero-score { font-size: 3rem; font-weight: 800; line-height: 1.1; } #benchmark-dashboard .hero-label { font-size: 1.1rem; color: var(--md-default-fg-color--light); margin-bottom: 0.5rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; } #benchmark-dashboard .hero-trend { font-size: 0.9rem; margin-top: 0.5rem; } #benchmark-dashboard .hero-explanation { font-size: 0.85rem; opacity: 0.6; max-width: 600px; margin: 0 auto; } /* Filter bar */ #benchmark-dashboard .filter-bar { display: flex; gap: 1rem; margin: 0.5rem 0 1rem 0; align-items: center; flex-wrap: wrap; } #benchmark-dashboard .filter-bar select { padding: 0.3rem 0.6rem; border-radius: 4px; border: 1px solid var(--md-default-fg-color--lightest); background: var(--md-default-bg-color); color: var(--md-default-fg-color); font-size: 0.85rem; } #benchmark-dashboard .filter-bar label { font-size: 0.85rem; font-weight: 500; } /* Run history expandable rows */ #benchmark-dashboard .run-detail { background: rgba(255,255,255,0.02); } #benchmark-dashboard .run-detail td:first-child { padding-left: 2rem; font-size: 0.85rem; opacity: 0.85; } #benchmark-dashboard .run-detail.hidden { display: none; } #benchmark-dashboard .toggle-details { background: none; border: 1px solid var(--md-default-fg-color--lightest); border-radius: 4px; cursor: pointer; padding: 0.15rem 0.5rem; font-size: 0.8rem; color: var(--md-default-fg-color); } #benchmark-dashboard .toggle-details:hover { background: var(--md-default-fg-color--lightest); } /* Trend indicators */ #benchmark-dashboard .trend-up { color: #22863a; } #benchmark-dashboard .trend-down { color: #cb2431; } #benchmark-dashboard .trend-neutral { color: #888; } /* Trace analysis */ #benchmark-dashboard .trace-analysis-row td { padding: 0 0.8rem 0.8rem 2rem; } #benchmark-dashboard .trace-content { font-family: var(--md-code-font-family, monospace); font-size: 0.8rem; background: rgba(128,128,128,0.1); padding: 0.8rem; border-radius: 4px; white-space: pre-wrap; max-height: 400px; overflow-y: auto; line-height: 1.5; } #benchmark-dashboard .trace-link { font-size: 0.8rem; margin-left: 0.5rem; opacity: 0.7; } #benchmark-dashboard .trace-link:hover { opacity: 1; } /* Pagination controls */ #benchmark-dashboard .pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 16px 0; flex-wrap: wrap; } #benchmark-dashboard .pagination button { background: #2a2a3e; color: #e0e0e0; border: 1px solid #444; border-radius: 6px; padding: 6px 12px; cursor: pointer; font-size: 0.85rem; } #benchmark-dashboard .pagination button:hover:not(:disabled) { background: #3a3a5e; } #benchmark-dashboard .pagination button:disabled { opacity: 0.4; cursor: not-allowed; } #benchmark-dashboard .pagination button.active { background: #667eea; border-color: #667eea; } #benchmark-dashboard .pagination .page-info { color: #aaa; font-size: 0.85em; } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script> <script> const REPO = 'akashgit/remote-factory'; const JSONL_URL = `https://raw.githubusercontent.com/${REPO}/benchmark-data/results.jsonl`; function isDarkMode() { return document.body.getAttribute('data-md-color-scheme') === 'slate'; } function chartColors() { const dark = isDarkMode(); return { text: dark ? '#ccc' : '#333', grid: dark ? '#444' : '#e0e0e0', }; } function formatDurationShort(seconds) { if (seconds == null) return '—'; const m = Math.floor(seconds / 60); const s = Math.round(seconds % 60); return m > 0 ? m + 'm ' + s + 's' : s + 's'; } function formatCost(usd) { if (usd == null) return '—'; return '$' + usd.toFixed(2); } function formatDate(ts) { if (!ts) return '—'; const d = parseTimestamp(ts); if (!d) return ts; return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) + ' ' + d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } function parseTimestamp(ts) { if (!ts) return null; const iso = ts.replace( /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/, '$1-$2-$3T$4:$5:$6Z' ); const d = new Date(iso); return isNaN(d) ? null : d; } function statusIcon(resolved) { return resolved ? 'PASS' : 'FAIL'; } function getSolver(r) { return r.solver || r.details?.solver || 'unknown'; } function isMainBranch(r) { return r.ref === 'refs/heads/main'; } function isPR(r) { return (r.ref && r.ref.startsWith('refs/pull/')) || !!r.pr_number; } function formatRefLabel(r) { if (!r.ref) return '—'; if (r.ref === 'refs/heads/main') return 'main'; if (r.ref.startsWith('refs/pull/')) return 'PR #' + r.ref.replace('refs/pull/', '').replace('/merge', ''); return r.ref.replace('refs/heads/', ''); } async function fetchJsonl() { const resp = await fetch(JSONL_URL); if (!resp.ok) return null; const text = await resp.text(); const lines = text.trim().split('\n').filter(Boolean); return lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); } function getLatestByCombo(results) { const byKey = {}; for (const r of results) { const solver = getSolver(r); const key = r.benchmark + '|' + solver; if (!byKey[key] || (r.timestamp > byKey[key].timestamp)) { byKey[key] = r; } } return byKey; } function groupByRunId(results) { const runs = {}; for (const entry of results) { const rid = entry.run_id || entry.timestamp || Math.random().toString(); if (!runs[rid]) { runs[rid] = { run_id: rid, date: entry.timestamp, ref: entry.ref, commit: entry.commit, run_url: entry.run_url, trigger: entry.trigger, jobs: [] }; } runs[rid].jobs.push(entry); } for (const run of Object.values(runs)) { run.passed = run.jobs.filter(j => j.resolved).length; run.total = run.jobs.length; run.totalCost = run.jobs.reduce((s, j) => s + (j.details?.cost_usd || 0), 0); run.maxDuration = Math.max(...run.jobs.map(j => j.duration_seconds || 0)); run.branch = formatRefLabel(run); run.passRate = run.total > 0 ? (run.passed / run.total * 100) : 0; run.score = run.total > 0 ? run.passRate.toFixed(0) + '%' : '—'; } return Object.values(runs).sort((a, b) => (b.date || '').localeCompare(a.date || '') ); } function computeRunAccuracy(jobs) { const resolved = jobs.filter(j => j.resolved).length; return jobs.length > 0 ? (resolved / jobs.length) * 100 : null; } function computeAccuracyPoints(mainResults) { const runsByRunId = {}; for (const e of mainResults) { const rid = e.run_id || e.timestamp; if (!runsByRunId[rid]) runsByRunId[rid] = []; runsByRunId[rid].push(e); } const factoryPoints = []; const claudePoints = []; for (const [, entries] of Object.entries(runsByRunId)) { const date = parseTimestamp(entries[0].timestamp); if (!date) continue; const factoryJobs = entries.filter(e => getSolver(e) === 'factory'); const claudeJobs = entries.filter(e => getSolver(e) === 'claude-code'); if (factoryJobs.length > 0) { factoryPoints.push({ x: date, y: computeRunAccuracy(factoryJobs) }); } if (claudeJobs.length > 0) { claudePoints.push({ x: date, y: computeRunAccuracy(claudeJobs) }); } } factoryPoints.sort((a, b) => a.x - b.x); claudePoints.sort((a, b) => a.x - b.x); return { factoryPoints, claudePoints }; } function computeRunAverages(mainResults) { const runsByRunId = {}; for (const e of mainResults) { const rid = e.run_id || e.timestamp || ''; if (!runsByRunId[rid]) runsByRunId[rid] = []; runsByRunId[rid].push(e); } const factoryDurationPoints = []; const claudeDurationPoints = []; const factoryCostPoints = []; const claudeCostPoints = []; for (const [, entries] of Object.entries(runsByRunId)) { const date = parseTimestamp(entries[0].timestamp); if (!date) continue; const factoryJobs = entries.filter(e => getSolver(e) === 'factory'); const claudeJobs = entries.filter(e => getSolver(e) === 'claude-code'); if (factoryJobs.length > 0) { const durs = factoryJobs.filter(j => j.duration_seconds != null); const costs = factoryJobs.filter(j => j.details?.cost_usd != null); if (durs.length > 0) factoryDurationPoints.push({ x: date, y: durs.reduce((s, j) => s + j.duration_seconds, 0) / durs.length }); if (costs.length > 0) factoryCostPoints.push({ x: date, y: costs.reduce((s, j) => s + j.details.cost_usd, 0) / costs.length }); } if (claudeJobs.length > 0) { const durs = claudeJobs.filter(j => j.duration_seconds != null); const costs = claudeJobs.filter(j => j.details?.cost_usd != null); if (durs.length > 0) claudeDurationPoints.push({ x: date, y: durs.reduce((s, j) => s + j.duration_seconds, 0) / durs.length }); if (costs.length > 0) claudeCostPoints.push({ x: date, y: costs.reduce((s, j) => s + j.details.cost_usd, 0) / costs.length }); } } const sortByDate = (a, b) => a.x - b.x; factoryDurationPoints.sort(sortByDate); claudeDurationPoints.sort(sortByDate); factoryCostPoints.sort(sortByDate); claudeCostPoints.sort(sortByDate); return { factoryDurationPoints, claudeDurationPoints, factoryCostPoints, claudeCostPoints }; } // Section 1: Hero — Headline Numbers function renderHero(mainResults) { const runsByRunId = {}; for (const e of mainResults) { const rid = e.run_id || e.timestamp; if (!runsByRunId[rid]) runsByRunId[rid] = []; runsByRunId[rid].push(e); } const runIds = Object.keys(runsByRunId).sort((a, b) => { const ta = runsByRunId[a][0].timestamp || ''; const tb = runsByRunId[b][0].timestamp || ''; return tb.localeCompare(ta); }); if (runIds.length === 0) return ''; const latestEntries = runsByRunId[runIds[0]]; const priorEntries = runIds.length > 1 ? runsByRunId[runIds[1]] : []; function solverAccuracy(entries, solver) { const jobs = entries.filter(e => getSolver(e) === solver); return computeRunAccuracy(jobs); } const factoryAcc = solverAccuracy(latestEntries, 'factory'); const claudeAcc = solverAccuracy(latestEntries, 'claude-code'); const prevFactoryAcc = solverAccuracy(priorEntries, 'factory'); const prevClaudeAcc = solverAccuracy(priorEntries, 'claude-code'); function trendHtml(current, prev) { if (current == null) return ''; if (prev == null) return '
first run
'; const delta = current - prev; if (Math.abs(delta) < 0.5) return '
= no change
'; const sign = delta > 0 ? '+' : ''; const cls = delta > 0 ? 'trend-up' : 'trend-down'; const arrow = delta > 0 ? '▲' : '▼'; return `
${arrow} ${sign}${delta.toFixed(0)}% vs prior run
`; } let html = '
'; html += '
'; html += '
'; html += '
Factory
'; html += `
${factoryAcc != null ? factoryAcc.toFixed(0) + '%' : '—'}
`; html += trendHtml(factoryAcc, prevFactoryAcc); html += '
'; html += '
'; html += '
Claude Code
'; html += `
${claudeAcc != null ? claudeAcc.toFixed(0) + '%' : '—'}
`; html += trendHtml(claudeAcc, prevClaudeAcc); html += '
'; html += '
'; html += '
Latest main branch results, averaged across all benchmarks. Accuracy = percentage of benchmark tasks solved correctly (higher is better).
'; html += '
'; return html; } // Section 2: Accuracy Trend — Line Chart Over Time function renderAccuracyTrend(mainResults) { const { factoryPoints, claudePoints } = computeAccuracyPoints(mainResults); if (factoryPoints.length === 0 && claudePoints.length === 0) return ''; let html = '
Accuracy Over Time
'; html += '

Average solve rate across all benchmarks for each CI run on the main branch.

'; html += '
'; setTimeout(() => { const ctx = document.getElementById('accuracy-trend-chart'); if (!ctx) return; const colors = chartColors(); new Chart(ctx, { type: 'line', data: { datasets: [ { label: 'Factory', data: factoryPoints, borderColor: '#4285f4', backgroundColor: '#4285f4', tension: 0, pointRadius: 4, }, { label: 'Claude Code', data: claudePoints, borderColor: '#ff7043', backgroundColor: '#ff7043', tension: 0, pointRadius: 4, }, ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: colors.text } }, tooltip: { callbacks: { label: (item) => item.dataset.label + ': ' + item.raw.y.toFixed(0) + '%', }, }, }, scales: { x: { type: 'time', time: { unit: 'day', tooltipFormat: 'MMM d, yyyy HH:mm' }, title: { display: true, text: 'Date', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, y: { min: 0, max: 100, title: { display: true, text: 'Accuracy (%)', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, }, }, }); }, 0); return html; } // Section 3: Duration Trend — Line Chart Over Time function renderDurationTrend(mainResults) { const runAvgs = computeRunAverages(mainResults); if (runAvgs.factoryDurationPoints.length === 0 && runAvgs.claudeDurationPoints.length === 0) return ''; let html = '
Average Duration Over Time
'; html += '

Average wall-clock time per benchmark task. Lower is better.

'; html += '
'; setTimeout(() => { const ctx = document.getElementById('duration-trend-chart'); if (!ctx) return; const colors = chartColors(); new Chart(ctx, { type: 'line', data: { datasets: [ { label: 'Factory', data: runAvgs.factoryDurationPoints, borderColor: '#4285f4', backgroundColor: '#4285f4', tension: 0, pointRadius: 4, }, { label: 'Claude Code', data: runAvgs.claudeDurationPoints, borderColor: '#ff7043', backgroundColor: '#ff7043', tension: 0, pointRadius: 4, }, ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: colors.text } }, tooltip: { callbacks: { label: (item) => item.dataset.label + ': ' + formatDurationShort(item.raw.y), }, }, }, scales: { x: { type: 'time', time: { unit: 'day', tooltipFormat: 'MMM d, yyyy HH:mm' }, title: { display: true, text: 'Date', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, y: { title: { display: true, text: 'Duration (seconds)', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, }, }, }); }, 0); return html; } // Section 4: Cost Trend — Line Chart Over Time function renderCostTrend(mainResults) { const runAvgs = computeRunAverages(mainResults); if (runAvgs.factoryCostPoints.length === 0 && runAvgs.claudeCostPoints.length === 0) return ''; const totalFactory = runAvgs.factoryCostPoints.reduce((s, p) => s + p.y, 0); const totalClaude = runAvgs.claudeCostPoints.reduce((s, p) => s + p.y, 0); let html = '
Average Cost Per Run Over Time
'; html += '
'; html += `Total Spend: Factory ${formatCost(totalFactory)} | Claude Code ${formatCost(totalClaude)}`; html += '
'; html += '

Average API cost per benchmark task. Vertex AI runs may show $0 when billing is at the platform level.

'; html += '
'; setTimeout(() => { const ctx = document.getElementById('cost-trend-chart'); if (!ctx) return; const colors = chartColors(); new Chart(ctx, { type: 'line', data: { datasets: [ { label: 'Factory', data: runAvgs.factoryCostPoints, borderColor: '#4285f4', backgroundColor: '#4285f4', tension: 0, pointRadius: 4, }, { label: 'Claude Code', data: runAvgs.claudeCostPoints, borderColor: '#ff7043', backgroundColor: '#ff7043', tension: 0, pointRadius: 4, }, ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: colors.text } }, tooltip: { callbacks: { label: (item) => item.dataset.label + ': ' + formatCost(item.raw.y), }, }, }, scales: { x: { type: 'time', time: { unit: 'day', tooltipFormat: 'MMM d, yyyy HH:mm' }, title: { display: true, text: 'Date', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, y: { title: { display: true, text: 'Cost (USD)', color: colors.text }, ticks: { color: colors.text }, grid: { color: colors.grid }, }, }, }, }); }, 0); return html; } // Section 5: Per-Benchmark Breakdown Table function renderPerBenchmarkTable(mainResults) { const byKey = getLatestByCombo(mainResults); const combos = Object.entries(byKey).sort(([a], [b]) => a.localeCompare(b)); if (combos.length === 0) { return '
Latest Results by Benchmark
' + '

No main branch results yet.

'; } let html = '
Latest Results by Benchmark
'; html += '

Most recent main branch result for each benchmark and solver combination.

'; html += ''; html += ''; html += ''; html += ''; for (const [, r] of combos) { const solver = getSolver(r); const commit = r.commit ? r.commit.substring(0, 7) : '—'; const commitLink = r.commit ? `${commit}` : commit; const runLink = r.run_url ? `details` : '—'; html += ''; html += ``; html += ``; html += ``; html += ``; html += ``; const traceSummary = r.trace_summary || ''; const escaped = traceSummary.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); const truncated = escaped.length > 80 ? escaped.substring(0, 77) + '...' : escaped; const traceHtml = r.trace_url ? '' + (truncated || 'trace') + '' : (truncated || '—'); html += ''; html += ``; html += ``; html += ''; } html += '
Benchmark Solver Result Duration Cost Trace Commit Run
${r.benchmark} ${solver} ${statusIcon(r.resolved)} ${(r.score * 100).toFixed(0)}% ${formatDurationShort(r.duration_seconds)} ${formatCost(r.details?.cost_usd)} ' + traceHtml + ' ${commitLink} ${runLink}
'; return html; } function formatTrendColumn(delta) { if (!delta) return ''; let parts = []; const accVal = delta.accuracy; if (Math.abs(accVal) >= 0.5) { const sign = accVal > 0 ? '+' : ''; const cls = accVal > 0 ? 'trend-up' : 'trend-down'; const arrow = accVal > 0 ? '▲' : '▼'; parts.push(`${arrow}${sign}${accVal.toFixed(0)}% acc`); } const costVal = delta.cost; if (Math.abs(costVal) >= 0.01) { const sign = costVal > 0 ? '+' : ''; const cls = costVal > 0 ? 'trend-down' : 'trend-up'; const arrow = costVal > 0 ? '▲' : '▼'; parts.push(`${arrow}${sign}$${costVal.toFixed(2)} cost`); } const durVal = delta.duration; if (Math.abs(durVal) >= 1) { const sign = durVal > 0 ? '+' : ''; const cls = durVal > 0 ? 'trend-down' : 'trend-up'; const arrow = durVal > 0 ? '▲' : '▼'; parts.push(`${arrow}${sign}${formatDurationShort(Math.abs(durVal))} dur`); } return parts.length > 0 ? parts.join('
') : '= no change'; } // Section 6: Run History — paginated with expandable details let currentPage = 1; const RUNS_PER_PAGE = 20; let allSortedRuns = []; function getFilteredRuns() { const bv = document.getElementById('filter-benchmark')?.value || ''; const sv = document.getElementById('filter-solver')?.value || ''; return allSortedRuns.filter(run => { const runBenchmarks = [...new Set(run.jobs.map(j => j.benchmark))]; const runSolvers = [...new Set(run.jobs.map(j => getSolver(j)))]; return (!bv || runBenchmarks.includes(bv)) && (!sv || runSolvers.includes(sv)); }); } function renderPaginationControls(current, total, totalItems, startIdx, endIdx) { if (totalItems === 0) return 'No matching runs'; if (total <= 1) return `Showing all ${totalItems} runs`; let html = `Showing runs ${startIdx + 1}–${endIdx} of ${totalItems}`; html += `← Prev`; const pages = []; pages.push(1); if (current > 3) pages.push('...'); for (let p = Math.max(2, current - 1); p <= Math.min(total - 1, current + 1); p++) { pages.push(p); } if (current < total - 2) pages.push('...'); if (total > 1) pages.push(total); for (const p of pages) { if (p === '...') { html += ''; } else { html += `${p}`; } } html += `Next →`; return html; } function updateRunHistoryTable() { const filteredRuns = getFilteredRuns(); const totalPages = Math.max(1, Math.ceil(filteredRuns.length / RUNS_PER_PAGE)); if (currentPage > totalPages) currentPage = totalPages; const start = (currentPage - 1) * RUNS_PER_PAGE; const pageRuns = filteredRuns.slice(start, start + RUNS_PER_PAGE); const bv = document.getElementById('filter-benchmark')?.value || ''; const sv = document.getElementById('filter-solver')?.value || ''; let rowsHtml = ''; for (const run of pageRuns) { const rid = run.run_id.replace(/[^a-zA-Z0-9_-]/g, '_'); const jobBenchmarks = [...new Set(run.jobs.map(j => j.benchmark))].join(','); const jobSolvers = [...new Set(run.jobs.map(j => getSolver(j)))].join(','); rowsHtml += ``; rowsHtml += `${formatDate(run.date)}`; rowsHtml += `${run.branch}`; rowsHtml += `${run.score} (${run.passed}/${run.total})`; rowsHtml += `${formatCost(run.totalCost)}`; rowsHtml += `${formatDurationShort(run.maxDuration)}`; rowsHtml += `${formatTrendColumn(run.delta)}`; rowsHtml += `+`; rowsHtml += ''; for (const j of run.jobs) { const solver = getSolver(j); const matchB = !bv || j.benchmark === bv; const matchS = !sv || solver === sv; if (!matchB || !matchS) continue; const runLink = j.run_url ? `link` : ''; const traceLink = j.trace_url ? `trace` : ''; rowsHtml += ``; rowsHtml += `${j.benchmark}`; rowsHtml += `${solver}`; rowsHtml += `${statusIcon(j.resolved)} ${(j.score * 100).toFixed(0)}%`; rowsHtml += `${formatCost(j.details?.cost_usd)}`; rowsHtml += `${formatDurationShort(j.duration_seconds)}`; rowsHtml += ``; rowsHtml += `${runLink}${traceLink ? ' ' + traceLink : ''}`; rowsHtml += ''; if (j.trace_analysis) { const analysisTraceLink = j.trace_url ? ' View in Langfuse →' : ''; rowsHtml += ``; rowsHtml += '
Trace Analysis' + analysisTraceLink + ''; rowsHtml += '
' + j.trace_analysis.replace(//g, '>').replace(/\n/g, '
') + '
'; rowsHtml += '
'; } } } const tbody = document.getElementById('history-tbody'); if (tbody) tbody.innerHTML = rowsHtml; const endIdx = start + pageRuns.length; const paginationHtml = renderPaginationControls(currentPage, totalPages, filteredRuns.length, start, endIdx); const pTop = document.getElementById('pagination-top'); const pBottom = document.getElementById('pagination-bottom'); if (pTop) pTop.innerHTML = paginationHtml; if (pBottom) pBottom.innerHTML = paginationHtml; } function renderRunHistory(allResults) { const benchmarks = [...new Set(allResults.map(r => r.benchmark))].sort(); const solvers = [...new Set(allResults.map(r => getSolver(r)))].sort(); const sortedRuns = groupByRunId(allResults); for (let i = 0; i < sortedRuns.length; i++) { const run = sortedRuns[i]; const prev = sortedRuns[i + 1]; if (!prev) { run.delta = null; continue; } run.delta = { accuracy: run.passRate - prev.passRate, cost: run.totalCost - prev.totalCost, duration: run.maxDuration - prev.maxDuration, }; } allSortedRuns = sortedRuns; currentPage = 1; let html = '
Run History
'; html += '

All benchmark runs, newest first. Trend arrows compare each run against the immediately preceding run. Green = improvement, red = regression.

'; html += '
'; html += 'Benchmark: All'; for (const b of benchmarks) html += `${b}`; html += ''; html += 'Solver: All'; for (const s of solvers) html += `${s}`; html += ''; html += '
'; html += '
'; html += ''; html += ''; html += ''; html += '
DateBranchPass RateCostDurationTrend
'; html += '
'; setTimeout(() => { updateRunHistoryTable(); document.addEventListener('click', (e) => { const btn = e.target.closest('.toggle-details'); if (!btn) return; const runId = btn.dataset.target; const details = document.querySelectorAll(`.run-detail[data-parent="${runId}"]`); const isHidden = details[0]?.classList.contains('hidden'); details.forEach(row => row.classList.toggle('hidden')); btn.textContent = isHidden ? '−' : '+'; }); document.addEventListener('click', (e) => { const btn = e.target.closest('.page-btn'); if (!btn || btn.disabled) return; const page = parseInt(btn.dataset.page); if (page && page !== currentPage) { currentPage = page; updateRunHistoryTable(); } }); const benchSelect = document.getElementById('filter-benchmark'); const solverSelect = document.getElementById('filter-solver'); if (benchSelect) benchSelect.addEventListener('change', () => { currentPage = 1; updateRunHistoryTable(); }); if (solverSelect) solverSelect.addEventListener('change', () => { currentPage = 1; updateRunHistoryTable(); }); }, 0); return html; } // Section 7: PR Comparison function renderPRComparison(allResults) { const prResults = allResults.filter(isPR); if (prResults.length === 0) return ''; const mainResults = allResults.filter(isMainBranch); const mainLatest = getLatestByCombo(mainResults); let html = '
Pull Request Comparison
'; html += '

Pull request benchmark results compared against the latest main branch baseline.

'; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; const sortedPR = [...prResults].sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || '') ); for (const r of sortedPR) { const solver = getSolver(r); const key = r.benchmark + '|' + solver; const baseline = mainLatest[key]; const prLabel = r.pr_number ? `#${r.pr_number}` : r.ref ? r.ref.replace('refs/pull/', '').replace('/merge', '') : '—'; const prScore = r.score; const mainScore = baseline ? baseline.score : null; const scoreDelta = (prScore != null && mainScore != null) ? prScore - mainScore : null; const prCost = r.details?.cost_usd; const mainCost = baseline?.details?.cost_usd; const costDelta = (prCost != null && mainCost != null) ? prCost - mainCost : null; const prDur = r.duration_seconds; const mainDur = baseline?.duration_seconds; function formatDelta(val, invert) { if (val == null) return '—'; const sign = val > 0 ? '+' : ''; const cls = val > 0 ? (invert ? 'trend-down' : 'trend-up') : val < 0 ? (invert ? 'trend-up' : 'trend-down') : 'trend-neutral'; return `${sign}${typeof val === 'number' && Math.abs(val) < 10 ? val.toFixed(2) : val}`; } html += ''; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; html += ''; } html += '
PR Benchmark Solver PR Score Main Score Delta PR Cost Main Cost Delta PR Duration Main Duration
${prLabel} ${r.benchmark} ${solver} ${prScore != null ? (prScore * 100).toFixed(0) + '%' : '—'} ${mainScore != null ? (mainScore * 100).toFixed(0) + '%' : '—'} ${formatDelta(scoreDelta, false)} ${formatCost(prCost)} ${formatCost(mainCost)} ${formatDelta(costDelta != null ? Number(costDelta.toFixed(2)) : null, true)} ${formatDurationShort(prDur)} ${formatDurationShort(mainDur)}
'; return html; } async function renderDashboard() { const container = document.getElementById('benchmark-dashboard'); try { const allResults = await fetchJsonl(); if (allResults && allResults.length > 0) { const mainResults = allResults.filter(isMainBranch); let html = ''; html += renderHero(mainResults); html += renderAccuracyTrend(mainResults); html += renderDurationTrend(mainResults); html += renderCostTrend(mainResults); html += renderPerBenchmarkTable(mainResults); html += renderRunHistory(allResults); html += renderPRComparison(allResults); container.innerHTML = html; return; } container.innerHTML = '

No benchmark data available yet.
' + 'Run the benchmark workflow to generate initial data: Actions → Benchmark CI → Run workflow

'; } catch (err) { container.innerHTML = `

${err.message}

`; } } renderDashboard(); </script>