diff --git a/model-benchmarks/docs/assets/eq-public-after.png b/model-benchmarks/docs/assets/eq-public-after.png new file mode 100644 index 0000000..0970cff Binary files /dev/null and b/model-benchmarks/docs/assets/eq-public-after.png differ diff --git a/model-benchmarks/docs/assets/eq-public-before.png b/model-benchmarks/docs/assets/eq-public-before.png new file mode 100644 index 0000000..fa15c98 Binary files /dev/null and b/model-benchmarks/docs/assets/eq-public-before.png differ diff --git a/model-benchmarks/docs/eq-public-column-proposal.md b/model-benchmarks/docs/eq-public-column-proposal.md new file mode 100644 index 0000000..5e46a41 --- /dev/null +++ b/model-benchmarks/docs/eq-public-column-proposal.md @@ -0,0 +1,66 @@ +# Proposal: render the public EQ-Bench score as its own column + +**Status:** DRAFT — needs Nick's approval before it reaches `main`. +**Branch:** `benchmark/eq-public-display` · **Scope:** display logic only, zero data changes. +**Date:** 2026-07-26 + +## The problem + +`model-data.json` holds 35 models. 28 of them carry a real, upstream-verified public EQ-Bench +v3 score. The page rendered only 12 of them, because the EQ column read a single field, +`v3_score`, which is Nick's locally-run 22-trait metric. Everything else showed an em-dash. + +Measured on the live page before this change: **23 of 35 EQ cells rendered "—"** while the +data behind them existed and was verified. Every pass that added coverage widened that gap. + +## What this change does + +Splits one EQ column into two clearly-labelled ones. + +| | Reads | Rubric | Renders | +|---|---|---|---| +| **EQ (local)** | `v3_score` | 22 traits, our own run (~$6/model) | 12 of 35 | +| **EQ (public)** | `public_rubric_0_100` | 17 traits, eqbench.com leaderboard | 28 of 35 | + +Both are sortable. Blank cells sort to the bottom. Mobile cards gained a matching +"EQ (public)" tile and the old ambiguous "EQ" label became "EQ (local)". + +## What it deliberately does NOT do + +- **Does not merge, average, or convert between the two metrics.** They are different rubrics + and the offset is not even consistent in sign (Grok 4.20 is 68.55 local, 55.8 public). + Each column shows only its own source. +- **Does not fall back.** A model with no local score shows blank in the local column + forever, not the public number wearing a local label. +- **Does not touch `model-data.json`.** `git diff --name-only` covers exactly two files: + `js/app.js` and `index.html`. + +Both column tooltips say in plain words that the rubrics differ and are not directly +comparable. The public tooltip names eqbench.com as the source. Trait values in the public +tooltip are shown as `x/20` because upstream traits are on a 0-20 scale, not 0-100. + +## Evidence actually verified + +Headless Chromium against a local server, reading the rendered DOM: + +- Before: 35 rows, **23 EQ cells empty**, 12 rendered. +- After: 35 rows, **28 public scores rendered**, 12 local scores rendered. +- Every rendered number cross-checked cell-by-cell against `model-data.json`: + **0 mismatches across all 35 rows and both columns.** +- Sorting by EQ (public) verified descending (83.3, 82.6, 82.6, 82.4, 82.4 ...) with all + blanks at the bottom. +- Header row reads: Model, Reasoning, Coding, Agentic, EQ (local), EQ (public), Chat, Speed, + Cost, Context. `colspan` updated 9 → 10 in all three placeholder states. +- Test suite: 26/26 pass. Pre-commit hooks pass including Prettier. + +Screenshots (tracked with this proposal): + +- [Before: one EQ column, 23 blanks](assets/eq-public-before.png) +- [After: separate local and public EQ columns](assets/eq-public-after.png) + +## Open question for Nick + +The top of the page by public score is Claude Opus 4.8 at 83.3, and the local column's top is +GPT-5.4 at 73.2. Two different models lead depending on the column. That is honest and it is +also a story worth telling in prose. Want a short explainer under the table saying why two EQ +columns exist and why they disagree? That is prose, so it is not in this diff. diff --git a/model-benchmarks/index.html b/model-benchmarks/index.html index 9c839a8..2bfc178 100644 --- a/model-benchmarks/index.html +++ b/model-benchmarks/index.html @@ -475,11 +475,22 @@

> - EQ + EQ (local) EQ-Bench v3 score (0-100) — measures emotional intelligence - including empathy, insight, social dexterity, and warmth. Hover - for Elo ranking and trait breakdown.EQ-Bench v3 run locally by us — 22-trait rubric, scale 0–100. + Measures emotional intelligence including empathy, insight, social + dexterity, and warmth. Hover a score for Elo and trait breakdown. + Not comparable to the public column: different rubric. + + + EQ (public) + Score as published on the public EQ-Bench v3 leaderboard at + eqbench.com — 17-trait rubric, scale 0–100. A different rubric + from our local column, so the two are not directly comparable and + are never averaged or substituted for one another. Blank means no + published result exists. diff --git a/model-benchmarks/js/app.js b/model-benchmarks/js/app.js index c9b17e0..b46a92e 100644 --- a/model-benchmarks/js/app.js +++ b/model-benchmarks/js/app.js @@ -40,7 +40,7 @@ async function init() { const tbody = document.getElementById("table-body"); const cards = document.getElementById("mobile-cards"); - tbody.innerHTML = `Loading models...`; + tbody.innerHTML = `Loading models...`; cards.innerHTML = `
Loading models...
`; try { @@ -61,7 +61,7 @@ bindEvents(); render(); } catch (err) { - tbody.innerHTML = `Failed to load model data. Try refreshing the page.`; + tbody.innerHTML = `Failed to load model data. Try refreshing the page.`; cards.innerHTML = `
Failed to load model data. Try refreshing the page.
`; } } @@ -132,6 +132,8 @@ return model.scores?.agentic ?? -1; case "eq_score": return model.benchmarks?.eq_bench?.v3_score ?? -1; + case "eq_public": + return model.benchmarks?.eq_bench?.public_rubric_0_100 ?? -1; case "arena_elo": return model.benchmarks?.arena?.elo ?? -1; case "speed": @@ -233,6 +235,38 @@ `; } + function eqPublicHtml(model) { + const eq = model.benchmarks?.eq_bench; + const val = eq?.public_rubric_0_100; + if (val == null) return `—`; + + const tooltipParts = ["Public EQ-Bench v3 leaderboard (17-trait rubric)"]; + if (eq.public_elo_norm) + tooltipParts.push(`Elo: ${Math.round(eq.public_elo_norm)}`); + + const traitKeys = [ + ["Empathy", "demonstrated_empathy"], + ["Insight", "depth_of_insight"], + ["Social", "social_dexterity"], + ["Warmth", "warmth"], + ["Humanlike", "humanlike"], + ]; + const traits = traitKeys + .map(([label, key]) => { + const v = eq.public_traits_17?.[key]; + return v != null ? `${label}: ${v}/20` : null; + }) + .filter(Boolean) + .join(" · "); + if (traits) tooltipParts.push(traits); + + return ` + + ${val.toFixed(1)} + ${tooltipParts.join("
")}
+
`; + } + function costHtml(pricing) { const blended = pricing?.blended_per_m; if (blended === 0 || blended == null) { @@ -273,7 +307,7 @@ function renderTable(list) { const tbody = document.getElementById("table-body"); if (!list.length) { - tbody.innerHTML = `No models match your filters.`; + tbody.innerHTML = `No models match your filters.`; return; } tbody.innerHTML = list @@ -290,6 +324,7 @@ ${scoreHtml(m.scores?.coding)} ${scoreHtml(m.scores?.agentic)} ${eqHtml(m)} + ${eqPublicHtml(m)} ${arenaHtml(m)} ${speedHtml(m.speed)} ${costHtml(m.pricing)} @@ -332,9 +367,13 @@
${m.scores?.agentic != null ? Math.round(m.scores.agentic) : "—"}
-
EQ
+
EQ (local)
${m.benchmarks?.eq_bench?.v3_score != null ? m.benchmarks.eq_bench.v3_score.toFixed(1) : "—"}
+
+
EQ (public)
+
${m.benchmarks?.eq_bench?.public_rubric_0_100 != null ? m.benchmarks.eq_bench.public_rubric_0_100.toFixed(1) : "—"}
+
Chat
${m.benchmarks?.arena?.elo ?? "—"}