ai-research-survey

Systematic scan of agentic development research. What's signal, what's noise.
git clone https://git.shiptheloop.com/ai-research-survey.git
Log | Files | Refs

dashboard.ts (7573B)


      1 import { loadDashboard, type Dashboard, type Pipeline } from '../data';
      2 import { renderHistogram } from '../components/histogram';
      3 import { renderBarChart } from '../components/bar-chart';
      4 
      5 const GAME_DESCRIPTIONS: Record<string, string> = {
      6   'Overclaiming': 'Abstract claims not supported by results, or results generalized beyond what was tested.',
      7   'Big Numbers No Error Bars': 'Reports results with no confidence intervals, no variance, and no uncertainty quantification.',
      8   'Contamination Dodge': 'Evaluates on benchmarks without discussing whether the model saw the test data during training.',
      9   'Open Source Theater': 'Releases code but provides no environment specs or reproduction instructions. Technically "open" but practically unreproducible.',
     10   'Trust Us': 'No raw data released and no code released. Claims are completely unverifiable by independent researchers.',
     11   'Moving Goalpost': 'Makes causal claims ("X improves Y") from observational data without a causal study design.',
     12   'The Black Box': 'No prompts provided and no hyperparameters reported. The experiment cannot be replicated even in principle.',
     13   'Cherry-picked Comparisons': 'Baselines are outdated or suspiciously weak. Newer, stronger alternatives exist but are not compared against.',
     14   'All Show No Substance': 'Strong evaluation design (baselines, metrics, ablations) but near-zero statistical rigor and no artifact release.',
     15   'Limitation Theater': 'Has a limitations section, but it contains only generic boilerplate rather than specific threats to this study.',
     16 };
     17 
     18 function renderProgressBar(p: Pipeline): string {
     19   const total = p.registry_total;
     20   const scanned = p.v5_opus + p.v5_haiku + p.deprecated_scan;
     21   const opusPct = (p.v5_opus / total * 100).toFixed(1);
     22   const haikuPct = (p.v5_haiku / total * 100).toFixed(1);
     23   const depPct = (p.deprecated_scan / total * 100).toFixed(1);
     24   const nonePct = (p.not_scanned / total * 100).toFixed(1);
     25   const scannedPct = (scanned / total * 100).toFixed(1);
     26 
     27   return `<div class="pipeline-bar">
     28     <div class="pipeline-header">
     29       <span class="pipeline-title">Survey Progress</span>
     30       <span class="pipeline-stat">${scanned} of ${total} scanned (${scannedPct}%)</span>
     31     </div>
     32     <div class="pipeline-track">
     33       <div class="pipeline-seg v5opus" style="width:${opusPct}%" title="V5 Opus: ${p.v5_opus}"></div>
     34       <div class="pipeline-seg v5haiku" style="width:${haikuPct}%" title="V5 Haiku/Sonnet: ${p.v5_haiku}"></div>
     35       <div class="pipeline-seg deprecated" style="width:${depPct}%" title="Deprecated scan: ${p.deprecated_scan}"></div>
     36       <div class="pipeline-seg notscan" style="width:${nonePct}%" title="Not scanned: ${p.not_scanned}"></div>
     37     </div>
     38     <div class="pipeline-legend">
     39       <span><span class="pipeline-dot v5opus"></span>V5 Opus (${p.v5_opus})</span>
     40       <span><span class="pipeline-dot v5haiku"></span>V5 Haiku/Sonnet (${p.v5_haiku})</span>
     41       <span><span class="pipeline-dot deprecated"></span>Deprecated (${p.deprecated_scan})</span>
     42       <span><span class="pipeline-dot notscan"></span>Not scanned (${p.not_scanned})</span>
     43     </div>
     44   </div>`;
     45 }
     46 
     47 export async function renderDashboard(app: HTMLElement) {
     48   app.innerHTML = '<div class="spinner"></div>';
     49   const agg = await loadDashboard();
     50 
     51   const topGame = Object.entries(agg.game_pcts).sort((a, b) => b[1] - a[1])[0];
     52 
     53   const p = agg.pipeline;
     54   const scanned = p.v5_opus + p.v5_haiku + p.deprecated_scan;
     55   const scanPct = Math.round(scanned / p.registry_total * 100);
     56 
     57   app.innerHTML = `
     58     ${renderProgressBar(p)}
     59 
     60     <div class="cards">
     61       <div class="card"><div class="label">Papers Analyzed</div><div class="value">${agg.n}</div></div>
     62       <div class="card"><div class="label">Median Score</div><div class="value yellow">${agg.median}%</div></div>
     63       <div class="card"><div class="label">Full Reproducibility</div><div class="value red">${agg.full_reproducibility_pct}%</div></div>
     64       <div class="card"><div class="label">${topGame ? topGame[0] : 'N/A'}</div><div class="value red">${topGame ? topGame[1] + '%' : '—'}</div></div>
     65     </div>
     66 
     67     <div class="section">
     68       <h2>Score Distribution</h2>
     69       ${renderHistogram(agg.histogram)}
     70     </div>
     71 
     72     <div class="section">
     73       <h2>Category Pass Rates</h2>
     74       ${renderBarChart(Object.entries(agg.category_rates).map(([k, v]) => ({ label: k, value: v })))}
     75     </div>
     76 
     77     <div class="section">
     78       <h2>Year Trends</h2>
     79       ${renderYearTrends(agg.year_trends)}
     80     </div>
     81 
     82     <div class="section">
     83       <h2>Named Games</h2>
     84       <p style="font-size:0.82rem;color:var(--text-dim);margin-bottom:0.75rem">Recurring methodological anti-patterns detected by checklist combinations. Percentage = share of papers exhibiting the pattern.</p>
     85       ${Object.entries(agg.game_pcts).sort((a, b) => b[1] - a[1]).map(([name, pct]) =>
     86         `<div class="game-row"><div><span class="game-name">${name}</span><div class="game-desc">${GAME_DESCRIPTIONS[name] || ''}</div></div><span class="game-pct">${pct}%</span></div>`
     87       ).join('')}
     88     </div>
     89 
     90     <div class="section">
     91       <h2>Archetypes</h2>
     92       ${Object.entries(agg.archetype_counts).sort((a, b) => b[1] - a[1]).map(([name, count]) =>
     93         `<div class="game-row"><span><span class="archetype ${name}">${name}</span></span><span style="color:var(--text-dim);font-family:var(--font)">${count} (${Math.round(count / agg.n * 100)}%)</span></div>`
     94       ).join('')}
     95     </div>
     96   `;
     97 }
     98 
     99 function renderYearTrends(trends: Record<string, { n: number; mean: number; median: number }>): string {
    100   const years = Object.keys(trends).map(Number).sort();
    101   if (years.length < 2) return '<p>Not enough data for trends.</p>';
    102 
    103   const w = 500, h = 180;
    104   const pad = { l: 50, r: 20, t: 10, b: 30 };
    105   const chartW = w - pad.l - pad.r;
    106   const chartH = h - pad.t - pad.b;
    107 
    108   const minY = 0, maxY = 100;
    109   const xScale = (y: number) => pad.l + ((y - years[0]) / (years[years.length - 1] - years[0])) * chartW;
    110   const yScale = (v: number) => pad.t + chartH - ((v - minY) / (maxY - minY)) * chartH;
    111 
    112   const medianPoints = years.map(y => `${xScale(y)},${yScale(trends[y].median)}`).join(' ');
    113   const meanPoints = years.map(y => `${xScale(y)},${yScale(trends[y].mean)}`).join(' ');
    114 
    115   let labels = years.map(y =>
    116     `<text x="${xScale(y)}" y="${h - 5}" text-anchor="middle">${y}</text>`
    117   ).join('');
    118 
    119   for (let v = 0; v <= 100; v += 25) {
    120     labels += `<text x="${pad.l - 8}" y="${yScale(v) + 4}" text-anchor="end">${v}%</text>`;
    121     labels += `<line class="grid-line" x1="${pad.l}" x2="${w - pad.r}" y1="${yScale(v)}" y2="${yScale(v)}" stroke-dasharray="3"/>`;
    122   }
    123 
    124   const dataLabels = years.map(y => {
    125     const t = trends[y];
    126     return `<text x="${xScale(y)}" y="${yScale(t.median) - 8}" text-anchor="middle" fill="var(--yellow)" font-size="10">${t.median}% (n=${t.n})</text>`;
    127   }).join('');
    128 
    129   return `<svg viewBox="0 0 ${w} ${h}" class="trend-chart" style="width:100%;max-width:${w}px">
    130     ${labels}
    131     <polyline points="${medianPoints}" fill="none" stroke="var(--yellow)" stroke-width="2"/>
    132     <polyline points="${meanPoints}" fill="none" stroke="var(--accent)" stroke-width="2" stroke-dasharray="4"/>
    133     ${years.map(y => `<circle cx="${xScale(y)}" cy="${yScale(trends[y].median)}" r="3" fill="var(--yellow)"/>`).join('')}
    134     ${dataLabels}
    135     <text x="${w - pad.r}" y="${pad.t + 12}" text-anchor="end" fill="var(--yellow)" font-size="10">median</text>
    136     <text x="${w - pad.r}" y="${pad.t + 24}" text-anchor="end" fill="var(--accent)" font-size="10">mean</text>
    137   </svg>`;
    138 }

Impressum · Datenschutz