Spaces:
Running
Running
Refactor to align on benchmark hierarchy
Browse files- app/evals/page.tsx +41 -43
- app/page.tsx +17 -10
- components/benchmark-detail.tsx +54 -61
- components/eval-detail.tsx +6 -5
- components/family-table.tsx +19 -39
- components/policy-overview.tsx +5 -5
- components/signals/corpus-signals-strip.tsx +1 -1
- lib/backend-artifacts.ts +78 -60
- lib/benchmark-schema.ts +9 -3
- lib/eval-processing.ts +5 -7
- lib/hf-data.ts +25 -377
- lib/model-data.ts +2 -2
- lib/sidecars.ts +22 -1
- lib/view-data.ts +11 -17
- tests/__snapshots__/adapters.test.ts.snap +0 -0
- tests/adapters.test.ts +3 -3
- tests/comparison-index-loader.test.ts +115 -0
- tests/eval-hierarchy-adapter.test.ts +54 -25
- tests/view-data.test.ts +6 -2
app/evals/page.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
import { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react"
|
|
|
|
| 4 |
import { ChevronDown, ChevronUp, Search, Tag } from "lucide-react"
|
| 5 |
|
| 6 |
-
import { FamilyTable } from "@/components/family-table"
|
| 7 |
import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
|
| 8 |
import { Navigation } from "@/components/navigation"
|
| 9 |
import type { EvalHierarchy, HierarchyFamily } from "@/lib/backend-artifacts"
|
|
@@ -18,42 +19,35 @@ type FamilySort = "results" | "benchmarks" | "name" | "category"
|
|
| 18 |
function familyEvalsCount(fam: HierarchyFamily): number {
|
| 19 |
if (fam.evals_count != null) return fam.evals_count
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
const benchmarks = fam.benchmarks ?? []
|
| 24 |
-
const leaves = fam.leaves ?? []
|
| 25 |
-
|
| 26 |
const allBenchmarks = [
|
| 27 |
-
...
|
| 28 |
-
...benchmarks,
|
| 29 |
-
...composites.flatMap((c) => c.benchmarks ?? []),
|
| 30 |
]
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
allBenchmarks.reduce(
|
| 35 |
-
(sum, b) => sum + ((b as { metrics?: unknown[] }).metrics?.length ?? 0),
|
| 36 |
-
0,
|
| 37 |
-
) +
|
| 38 |
-
leaves.reduce((sum, l) => sum + (l.evals_count ?? 0), 0)
|
| 39 |
)
|
| 40 |
}
|
| 41 |
|
| 42 |
function familyBenchmarkCount(fam: HierarchyFamily): number {
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
...composites.flatMap((c) => c.benchmarks ?? []),
|
| 52 |
-
]
|
| 53 |
-
return all.length > 0 ? all.length : leaves.length
|
| 54 |
}
|
| 55 |
|
| 56 |
export default function EvalsPage() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
const [hierarchy, setHierarchy] = useState<EvalHierarchy | null>(null)
|
| 58 |
const [totalModels, setTotalModels] = useState<number>(0)
|
| 59 |
const [evalItems, setEvalItems] = useState<Map<string, BenchmarkEvalListItem>>(new Map())
|
|
@@ -81,6 +75,23 @@ export default function EvalsPage() {
|
|
| 81 |
.finally(() => setLoading(false))
|
| 82 |
}, [])
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
const families = hierarchy?.families ?? []
|
| 85 |
|
| 86 |
// Build a domain → family-count map. The lite eval list doesn't carry
|
|
@@ -116,13 +127,7 @@ export default function EvalsPage() {
|
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
-
//
|
| 120 |
-
for (const leaf of fam.leaves ?? []) {
|
| 121 |
-
for (const d of leaf.tags?.domains ?? []) seen.add(d.trim().toLowerCase())
|
| 122 |
-
for (const d of lookupDomains(leaf.key)) seen.add(d.trim().toLowerCase())
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
// Family-level eval_summary_ids cover both shapes.
|
| 126 |
for (const id of fam.eval_summary_ids ?? []) {
|
| 127 |
for (const d of lookupDomains(id)) seen.add(d.trim().toLowerCase())
|
| 128 |
}
|
|
@@ -154,9 +159,6 @@ export default function EvalsPage() {
|
|
| 154 |
for (const benchmark of nestedBenchmarks) {
|
| 155 |
for (const d of benchmark.tags?.domains ?? []) recordLabel(d)
|
| 156 |
}
|
| 157 |
-
for (const leaf of fam.leaves ?? []) {
|
| 158 |
-
for (const d of leaf.tags?.domains ?? []) recordLabel(d)
|
| 159 |
-
}
|
| 160 |
}
|
| 161 |
for (const set of familyDomains.values()) {
|
| 162 |
for (const key of set) counts.set(key, (counts.get(key) ?? 0) + 1)
|
|
@@ -280,11 +282,7 @@ export default function EvalsPage() {
|
|
| 280 |
<div className="ec-page-meta-item">
|
| 281 |
<span className="ec-page-meta-item-l">Single benchmarks</span>
|
| 282 |
<span className="ec-page-meta-item-v">
|
| 283 |
-
{(
|
| 284 |
-
stats.benchmark_count ??
|
| 285 |
-
(stats.single_benchmark_count ?? 0) +
|
| 286 |
-
(stats.standalone_benchmark_count ?? 0)
|
| 287 |
-
).toLocaleString()}
|
| 288 |
</span>
|
| 289 |
</div>
|
| 290 |
<div className="ec-page-meta-item">
|
|
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
import { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react"
|
| 4 |
+
import { useRouter, useSearchParams } from "next/navigation"
|
| 5 |
import { ChevronDown, ChevronUp, Search, Tag } from "lucide-react"
|
| 6 |
|
| 7 |
+
import { FamilyTable, getFamilyNavId } from "@/components/family-table"
|
| 8 |
import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
|
| 9 |
import { Navigation } from "@/components/navigation"
|
| 10 |
import type { EvalHierarchy, HierarchyFamily } from "@/lib/backend-artifacts"
|
|
|
|
| 19 |
function familyEvalsCount(fam: HierarchyFamily): number {
|
| 20 |
if (fam.evals_count != null) return fam.evals_count
|
| 21 |
|
| 22 |
+
// v3 fallback: sum metric counts across the family's benchmarks (one
|
| 23 |
+
// of the three layout fields is present per family per spec §5.1).
|
|
|
|
|
|
|
|
|
|
| 24 |
const allBenchmarks = [
|
| 25 |
+
...(fam.standalone_benchmarks ?? []),
|
| 26 |
+
...(fam.benchmarks ?? []),
|
| 27 |
+
...((fam.composites ?? []).flatMap((c) => c.benchmarks ?? [])),
|
| 28 |
]
|
| 29 |
+
return allBenchmarks.reduce(
|
| 30 |
+
(sum, b) => sum + (b.metrics?.length ?? 0),
|
| 31 |
+
0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
)
|
| 33 |
}
|
| 34 |
|
| 35 |
function familyBenchmarkCount(fam: HierarchyFamily): number {
|
| 36 |
+
return (
|
| 37 |
+
(fam.standalone_benchmarks?.length ?? 0) +
|
| 38 |
+
(fam.benchmarks?.length ?? 0) +
|
| 39 |
+
((fam.composites ?? []).reduce(
|
| 40 |
+
(sum, c) => sum + (c.benchmarks?.length ?? 0),
|
| 41 |
+
0,
|
| 42 |
+
))
|
| 43 |
+
)
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
export default function EvalsPage() {
|
| 47 |
+
const router = useRouter()
|
| 48 |
+
const searchParams = useSearchParams()
|
| 49 |
+
const familyParam = searchParams.get("family")
|
| 50 |
+
|
| 51 |
const [hierarchy, setHierarchy] = useState<EvalHierarchy | null>(null)
|
| 52 |
const [totalModels, setTotalModels] = useState<number>(0)
|
| 53 |
const [evalItems, setEvalItems] = useState<Map<string, BenchmarkEvalListItem>>(new Map())
|
|
|
|
| 75 |
.finally(() => setLoading(false))
|
| 76 |
}, [])
|
| 77 |
|
| 78 |
+
// Resolve the `?family=<key>` deep link from the home page family cards.
|
| 79 |
+
// For families with a clean family-level summary we redirect to the
|
| 80 |
+
// detail page; for aggregator families (no nav target) we seed the
|
| 81 |
+
// search box so the listing narrows to that family and the user can
|
| 82 |
+
// expand it. Runs once per `family` param value, after data loads.
|
| 83 |
+
useEffect(() => {
|
| 84 |
+
if (!familyParam || !hierarchy) return
|
| 85 |
+
const fam = hierarchy.families.find((f) => f.key === familyParam)
|
| 86 |
+
if (!fam) return
|
| 87 |
+
const navId = getFamilyNavId(fam, benchmarkCards)
|
| 88 |
+
if (navId) {
|
| 89 |
+
router.replace(`/evals/${encodeURIComponent(navId)}`)
|
| 90 |
+
return
|
| 91 |
+
}
|
| 92 |
+
setSearchQuery(fam.display_name || fam.key)
|
| 93 |
+
}, [familyParam, hierarchy, benchmarkCards, router])
|
| 94 |
+
|
| 95 |
const families = hierarchy?.families ?? []
|
| 96 |
|
| 97 |
// Build a domain → family-count map. The lite eval list doesn't carry
|
|
|
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
| 130 |
+
// Family-level eval_summary_ids cover the v3 shape.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
for (const id of fam.eval_summary_ids ?? []) {
|
| 132 |
for (const d of lookupDomains(id)) seen.add(d.trim().toLowerCase())
|
| 133 |
}
|
|
|
|
| 159 |
for (const benchmark of nestedBenchmarks) {
|
| 160 |
for (const d of benchmark.tags?.domains ?? []) recordLabel(d)
|
| 161 |
}
|
|
|
|
|
|
|
|
|
|
| 162 |
}
|
| 163 |
for (const set of familyDomains.values()) {
|
| 164 |
for (const key of set) counts.set(key, (counts.get(key) ?? 0) + 1)
|
|
|
|
| 282 |
<div className="ec-page-meta-item">
|
| 283 |
<span className="ec-page-meta-item-l">Single benchmarks</span>
|
| 284 |
<span className="ec-page-meta-item-v">
|
| 285 |
+
{(stats.benchmark_count ?? 0).toLocaleString()}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
</span>
|
| 287 |
</div>
|
| 288 |
<div className="ec-page-meta-item">
|
app/page.tsx
CHANGED
|
@@ -53,14 +53,11 @@ export default async function HomePage() {
|
|
| 53 |
const stats = hierarchy.stats
|
| 54 |
const familyCount = stats?.family_count ?? hierarchy.families.length
|
| 55 |
const compositeCount = stats?.composite_count ?? 0
|
| 56 |
-
//
|
| 57 |
-
//
|
| 58 |
-
//
|
| 59 |
-
//
|
| 60 |
-
const
|
| 61 |
-
const standaloneBenchmarkCount = stats?.standalone_benchmark_count ?? 0
|
| 62 |
-
const benchmarkLeafCount =
|
| 63 |
-
stats?.benchmark_count ?? singleBenchmarkCount + standaloneBenchmarkCount
|
| 64 |
const sliceCount = stats?.slice_count ?? 0
|
| 65 |
const metricCount = stats?.metric_count ?? 0
|
| 66 |
const tripleCount = stats?.metric_rows_scanned ?? 0
|
|
@@ -77,9 +74,19 @@ export default async function HomePage() {
|
|
| 77 |
const generatedAt = formatGeneratedAt(manifest?.generated_at)
|
| 78 |
|
| 79 |
// Featured family cards — pick the first six families with summaries.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
const featuredFamilies = hierarchy.families.slice(0, 6).map((family) => {
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
let slices = 0
|
| 84 |
for (const b of benches) {
|
| 85 |
const benchSlices = (b as { slices?: unknown[] }).slices
|
|
|
|
| 53 |
const stats = hierarchy.stats
|
| 54 |
const familyCount = stats?.family_count ?? hierarchy.families.length
|
| 55 |
const compositeCount = stats?.composite_count ?? 0
|
| 56 |
+
// v3 hierarchy ships `benchmark_count` directly. The legacy
|
| 57 |
+
// `single_benchmark_count` / `standalone_benchmark_count` synthesis
|
| 58 |
+
// is gone with the adapter (Step 4b); v3's `benchmark_count` is the
|
| 59 |
+
// distinct (composite, benchmark) row count from the dim.
|
| 60 |
+
const benchmarkLeafCount = stats?.benchmark_count ?? 0
|
|
|
|
|
|
|
|
|
|
| 61 |
const sliceCount = stats?.slice_count ?? 0
|
| 62 |
const metricCount = stats?.metric_count ?? 0
|
| 63 |
const tripleCount = stats?.metric_rows_scanned ?? 0
|
|
|
|
| 74 |
const generatedAt = formatGeneratedAt(manifest?.generated_at)
|
| 75 |
|
| 76 |
// Featured family cards — pick the first six families with summaries.
|
| 77 |
+
// Curated multi-benchmark families (BFCL, MMLU, JudgeBench, …) put
|
| 78 |
+
// their benchmarks under composites[].benchmarks[] after the adapter;
|
| 79 |
+
// singletons land in standalone_benchmarks[] or benchmarks[]. Pull
|
| 80 |
+
// from all four shapes so the count reflects the union (matches
|
| 81 |
+
// family-table.tsx:313's logic).
|
| 82 |
const featuredFamilies = hierarchy.families.slice(0, 6).map((family) => {
|
| 83 |
+
// v3 family layouts: exactly one of standalone_benchmarks / benchmarks /
|
| 84 |
+
// composites is present per family. Walk all three to count benchmarks.
|
| 85 |
+
const benches: unknown[] = [
|
| 86 |
+
...(family.standalone_benchmarks ?? []),
|
| 87 |
+
...(family.benchmarks ?? []),
|
| 88 |
+
...((family.composites ?? []).flatMap((c) => c.benchmarks ?? [])),
|
| 89 |
+
]
|
| 90 |
let slices = 0
|
| 91 |
for (const b of benches) {
|
| 92 |
const benchSlices = (b as { slices?: unknown[] }).slices
|
components/benchmark-detail.tsx
CHANGED
|
@@ -105,38 +105,15 @@ interface CompositeGroup {
|
|
| 105 |
|
| 106 |
const INSTANCE_PREVIEW_LIMIT = 5
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
bfcl: "BFCL",
|
| 118 |
-
global_mmlu_lite: "Global MMLU Lite",
|
| 119 |
-
swe_bench: "SWE-bench",
|
| 120 |
-
arc_agi: "ARC-AGI",
|
| 121 |
-
tau_bench_2: "TAU-Bench 2",
|
| 122 |
-
ace: "ACE",
|
| 123 |
-
apex_agents: "APEX Agents",
|
| 124 |
-
apex_v1: "APEX v1",
|
| 125 |
-
appworld: "AppWorld",
|
| 126 |
-
browsecompplus: "BrowseComp+",
|
| 127 |
-
livecodebenchpro: "LiveCodeBench Pro",
|
| 128 |
-
sciarena: "SciArena",
|
| 129 |
-
terminal_bench_2_0: "Terminal Bench 2.0",
|
| 130 |
-
la_leaderboard: "LA Leaderboard",
|
| 131 |
-
theory_of_mind: "Theory of Mind",
|
| 132 |
-
fibble_arena: "Fibble Arena",
|
| 133 |
-
fibble1_arena: "Fibble Arena v1",
|
| 134 |
-
fibble2_arena: "Fibble Arena v2",
|
| 135 |
-
fibble3_arena: "Fibble Arena v3",
|
| 136 |
-
fibble4_arena: "Fibble Arena v4",
|
| 137 |
-
fibble5_arena: "Fibble Arena v5",
|
| 138 |
-
wordle_arena: "Wordle Arena",
|
| 139 |
-
}
|
| 140 |
|
| 141 |
const DISPLAY_TOKEN_OVERRIDES: Record<string, string> = {
|
| 142 |
ace: "ACE",
|
|
@@ -169,7 +146,6 @@ const DISPLAY_TOKEN_OVERRIDES: Record<string, string> = {
|
|
| 169 |
}
|
| 170 |
|
| 171 |
const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
|
| 172 |
-
...SUITE_DISPLAY_NAMES,
|
| 173 |
apex: "APEX",
|
| 174 |
apex_agents: "APEX Agents",
|
| 175 |
apex_v1: "APEX v1",
|
|
@@ -294,17 +270,17 @@ function doesLabelMatchSuiteKey(label: string | null | undefined, compositeKey:
|
|
| 294 |
|
| 295 |
function getCompositeKey(group: BenchmarkGroup): string {
|
| 296 |
const evaluation = group.variants[0]?.evaluation
|
| 297 |
-
const backendSuiteKey =
|
| 298 |
-
evaluation?.benchmark_parent_key ||
|
| 299 |
-
evaluation?.benchmark_family_key ||
|
| 300 |
-
evaluation?.benchmark
|
| 301 |
|
| 302 |
return normalizeCompositeKey(backendSuiteKey ?? group.key)
|
| 303 |
}
|
| 304 |
|
| 305 |
function getCompositeDisplayName(key: string): string {
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
| 308 |
}
|
| 309 |
|
| 310 |
function getCompositeName(group: BenchmarkGroup, compositeKey: string): string {
|
|
@@ -1426,11 +1402,12 @@ function buildBenchmarkGroups(
|
|
| 1426 |
(entry.evaluation.slice_name && (entry.evaluation.benchmark_parent_name || entry.evaluation.benchmark)
|
| 1427 |
? `${entry.evaluation.benchmark_parent_name || entry.evaluation.benchmark} / ${entry.evaluation.slice_name}`
|
| 1428 |
: title)
|
|
|
|
|
|
|
| 1429 |
const groupKey =
|
| 1430 |
entry.evaluation.eval_summary_id ??
|
| 1431 |
-
entry.evaluation.
|
| 1432 |
-
entry.evaluation.
|
| 1433 |
-
entry.evaluation.benchmark ??
|
| 1434 |
"benchmark"
|
| 1435 |
const card = benchmarkCards
|
| 1436 |
? lookupBenchmarkCard(benchmarkCards, rawBenchmarkName)
|
|
@@ -1988,7 +1965,7 @@ export function BenchmarkDetail({
|
|
| 1988 |
|
| 1989 |
// Family-bucketed groups for the list view, mirroring plotboxUnits logic.
|
| 1990 |
// When comparisonIndex is available we use the backend-authoritative
|
| 1991 |
-
//
|
| 1992 |
// BenchmarkGroup forms its own family.
|
| 1993 |
type ListFamily = {
|
| 1994 |
familyKey: string
|
|
@@ -2008,9 +1985,9 @@ export function BenchmarkDetail({
|
|
| 2008 |
?.evaluation.eval_summary_id
|
| 2009 |
const evalEntry =
|
| 2010 |
evalId && comparisonIndex ? comparisonIndex.evals[evalId] : null
|
| 2011 |
-
const famKey = evalEntry?.
|
| 2012 |
const famName =
|
| 2013 |
-
evalEntry?.
|
| 2014 |
evalEntry?.display_name ||
|
| 2015 |
group.title
|
| 2016 |
|
|
@@ -2338,7 +2315,12 @@ export function BenchmarkDetail({
|
|
| 2338 |
.filter((p) => selectedIds.has(p.model_route_id))
|
| 2339 |
.map((p) => ({
|
| 2340 |
modelId: p.model_route_id,
|
| 2341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2342 |
score: p.score,
|
| 2343 |
isCurrent: false,
|
| 2344 |
isDefault: defaults.has(p.model_route_id),
|
|
@@ -2406,7 +2388,7 @@ export function BenchmarkDetail({
|
|
| 2406 |
// A plotbox can expose a top-level "view" selector (slices, child
|
| 2407 |
// benchmarks, components) and an optional metric tab rail beneath the chart.
|
| 2408 |
// Plotbox grouping is driven entirely by comparison-index's own
|
| 2409 |
-
//
|
| 2410 |
type PlotboxMetricTab = {
|
| 2411 |
tabKey: string
|
| 2412 |
label: string
|
|
@@ -2474,9 +2456,9 @@ export function BenchmarkDetail({
|
|
| 2474 |
const evalEntry = comparisonIndex.evals[evalId]
|
| 2475 |
if (!evalEntry) continue
|
| 2476 |
|
| 2477 |
-
const famKey = evalEntry.
|
| 2478 |
const famName =
|
| 2479 |
-
evalEntry.
|
| 2480 |
const bucket = familyBuckets.get(famKey) ?? {
|
| 2481 |
familyName: famName,
|
| 2482 |
category: group.category,
|
|
@@ -2509,8 +2491,7 @@ export function BenchmarkDetail({
|
|
| 2509 |
histKey: histKeyFor(evalEntry.eval_summary_id, metric.metric_summary_id),
|
| 2510 |
evalSummaryId: evalEntry.eval_summary_id,
|
| 2511 |
metricSummaryId: metric.metric_summary_id,
|
| 2512 |
-
evalDisplayName:
|
| 2513 |
-
evalEntry.display_name || evalEntry.benchmark_leaf_name || group.title,
|
| 2514 |
evalEntry,
|
| 2515 |
metricEntry: metric,
|
| 2516 |
isRollup,
|
|
@@ -2526,8 +2507,7 @@ export function BenchmarkDetail({
|
|
| 2526 |
// One eval in scope — slices/splits become the view selector while
|
| 2527 |
// metrics move to a compact tab rail beneath the chart.
|
| 2528 |
const { group, evalEntry } = resolved[0]
|
| 2529 |
-
const evalDisplay =
|
| 2530 |
-
evalEntry.display_name || evalEntry.benchmark_leaf_name || group.title
|
| 2531 |
const singleEvalViewBuckets = new Map<
|
| 2532 |
string,
|
| 2533 |
{ viewKey: string; label: string; variants: BenchmarkVariant[] }
|
|
@@ -2598,21 +2578,22 @@ export function BenchmarkDetail({
|
|
| 2598 |
|
| 2599 |
// Multi-eval family — the view selector chooses among child evals and
|
| 2600 |
// each view exposes that eval's metrics in the bottom tab rail.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2601 |
const rollup =
|
| 2602 |
resolved.find(
|
| 2603 |
(r) =>
|
| 2604 |
-
r.evalEntry.
|
| 2605 |
-
r.evalEntry.
|
| 2606 |
) ?? null
|
| 2607 |
const children = rollup ? resolved.filter((r) => r !== rollup) : resolved
|
| 2608 |
const ordered: ResolvedGroup[] = rollup ? [rollup, ...children] : children
|
| 2609 |
|
| 2610 |
const views: PlotboxView[] = ordered
|
| 2611 |
.map((r) => {
|
| 2612 |
-
const rawLabel =
|
| 2613 |
-
r.evalEntry.benchmark_leaf_name ||
|
| 2614 |
-
r.evalEntry.display_name ||
|
| 2615 |
-
r.group.title
|
| 2616 |
const label =
|
| 2617 |
r === rollup ? "Overall" : stripFamilyPrefix(rawLabel, familyName)
|
| 2618 |
const tabs = r.evalEntry.metrics
|
|
@@ -2648,8 +2629,20 @@ export function BenchmarkDetail({
|
|
| 2648 |
let hasSlice = false
|
| 2649 |
let hasDistinctLeaves = false
|
| 2650 |
for (const r of children) {
|
| 2651 |
-
|
| 2652 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2653 |
hasDistinctLeaves = true
|
| 2654 |
}
|
| 2655 |
if (r.group.variants[0]?.evaluation.benchmark_component_key ?? null) {
|
|
|
|
| 105 |
|
| 106 |
const INSTANCE_PREVIEW_LIMIT = 5
|
| 107 |
|
| 108 |
+
// SUITE_DISPLAY_NAMES (38-entry hardcoded slug→display map) was deleted
|
| 109 |
+
// in Step 4c of the hierarchy-alignment work
|
| 110 |
+
// (notes/hierarchy-alignment.md §6 / §7 Step 4). The producer now ships
|
| 111 |
+
// curated display names for every family / composite / benchmark via
|
| 112 |
+
// prettify_display + the registry's display_overrides.yaml. This
|
| 113 |
+
// component reads the shipped name; the DISPLAY_TOKEN_OVERRIDES /
|
| 114 |
+
// DISPLAY_NAME_OVERRIDES below remain as a per-token polish layer
|
| 115 |
+
// (mostly for raw model identifier rendering, where the producer's
|
| 116 |
+
// metadata doesn't carry a curated display).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
const DISPLAY_TOKEN_OVERRIDES: Record<string, string> = {
|
| 119 |
ace: "ACE",
|
|
|
|
| 146 |
}
|
| 147 |
|
| 148 |
const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
|
|
|
|
| 149 |
apex: "APEX",
|
| 150 |
apex_agents: "APEX Agents",
|
| 151 |
apex_v1: "APEX v1",
|
|
|
|
| 270 |
|
| 271 |
function getCompositeKey(group: BenchmarkGroup): string {
|
| 272 |
const evaluation = group.variants[0]?.evaluation
|
| 273 |
+
const backendSuiteKey = evaluation?.family_id
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
return normalizeCompositeKey(backendSuiteKey ?? group.key)
|
| 276 |
}
|
| 277 |
|
| 278 |
function getCompositeDisplayName(key: string): string {
|
| 279 |
+
// Display names come from the shipped hierarchy.json. This helper
|
| 280 |
+
// is used in fallback paths where only a raw key is in scope; it
|
| 281 |
+
// applies the same per-token polish (DISPLAY_TOKEN_OVERRIDES) the
|
| 282 |
+
// rest of the renderer uses.
|
| 283 |
+
return normalizeDisplayLabel(key)
|
| 284 |
}
|
| 285 |
|
| 286 |
function getCompositeName(group: BenchmarkGroup, compositeKey: string): string {
|
|
|
|
| 1402 |
(entry.evaluation.slice_name && (entry.evaluation.benchmark_parent_name || entry.evaluation.benchmark)
|
| 1403 |
? `${entry.evaluation.benchmark_parent_name || entry.evaluation.benchmark} / ${entry.evaluation.slice_name}`
|
| 1404 |
: title)
|
| 1405 |
+
// eval_summary_id is producer-shipped on every v3 entry; the
|
| 1406 |
+
// remaining ?? tiers are for legacy snapshots without that field.
|
| 1407 |
const groupKey =
|
| 1408 |
entry.evaluation.eval_summary_id ??
|
| 1409 |
+
entry.evaluation.parent_benchmark_id ??
|
| 1410 |
+
entry.evaluation.family_id ??
|
|
|
|
| 1411 |
"benchmark"
|
| 1412 |
const card = benchmarkCards
|
| 1413 |
? lookupBenchmarkCard(benchmarkCards, rawBenchmarkName)
|
|
|
|
| 1965 |
|
| 1966 |
// Family-bucketed groups for the list view, mirroring plotboxUnits logic.
|
| 1967 |
// When comparisonIndex is available we use the backend-authoritative
|
| 1968 |
+
// family_id; otherwise we fall back to the group's own key so each
|
| 1969 |
// BenchmarkGroup forms its own family.
|
| 1970 |
type ListFamily = {
|
| 1971 |
familyKey: string
|
|
|
|
| 1985 |
?.evaluation.eval_summary_id
|
| 1986 |
const evalEntry =
|
| 1987 |
evalId && comparisonIndex ? comparisonIndex.evals[evalId] : null
|
| 1988 |
+
const famKey = evalEntry?.family_id ?? group.key
|
| 1989 |
const famName =
|
| 1990 |
+
evalEntry?.family_display_name ||
|
| 1991 |
evalEntry?.display_name ||
|
| 1992 |
group.title
|
| 1993 |
|
|
|
|
| 2315 |
.filter((p) => selectedIds.has(p.model_route_id))
|
| 2316 |
.map((p) => ({
|
| 2317 |
modelId: p.model_route_id,
|
| 2318 |
+
// Fall back to model_family_id when the registry has no display
|
| 2319 |
+
// name for this model. ~86% of score entries today land here;
|
| 2320 |
+
// root cause (registry coverage) is Step 2 work. The id is
|
| 2321 |
+
// already human-readable in this codebase ("anthropic/Sonnet 4.5"),
|
| 2322 |
+
// so the fallback is more useful than "Unknown Model".
|
| 2323 |
+
modelName: getModelDisplayName(p.model_family_name || p.model_family_id),
|
| 2324 |
score: p.score,
|
| 2325 |
isCurrent: false,
|
| 2326 |
isDefault: defaults.has(p.model_route_id),
|
|
|
|
| 2388 |
// A plotbox can expose a top-level "view" selector (slices, child
|
| 2389 |
// benchmarks, components) and an optional metric tab rail beneath the chart.
|
| 2390 |
// Plotbox grouping is driven entirely by comparison-index's own
|
| 2391 |
+
// family_id so it stays in sync with the backend.
|
| 2392 |
type PlotboxMetricTab = {
|
| 2393 |
tabKey: string
|
| 2394 |
label: string
|
|
|
|
| 2456 |
const evalEntry = comparisonIndex.evals[evalId]
|
| 2457 |
if (!evalEntry) continue
|
| 2458 |
|
| 2459 |
+
const famKey = evalEntry.family_id ?? evalId
|
| 2460 |
const famName =
|
| 2461 |
+
evalEntry.family_display_name || evalEntry.display_name || famKey
|
| 2462 |
const bucket = familyBuckets.get(famKey) ?? {
|
| 2463 |
familyName: famName,
|
| 2464 |
category: group.category,
|
|
|
|
| 2491 |
histKey: histKeyFor(evalEntry.eval_summary_id, metric.metric_summary_id),
|
| 2492 |
evalSummaryId: evalEntry.eval_summary_id,
|
| 2493 |
metricSummaryId: metric.metric_summary_id,
|
| 2494 |
+
evalDisplayName: evalEntry.display_name || group.title,
|
|
|
|
| 2495 |
evalEntry,
|
| 2496 |
metricEntry: metric,
|
| 2497 |
isRollup,
|
|
|
|
| 2507 |
// One eval in scope — slices/splits become the view selector while
|
| 2508 |
// metrics move to a compact tab rail beneath the chart.
|
| 2509 |
const { group, evalEntry } = resolved[0]
|
| 2510 |
+
const evalDisplay = evalEntry.display_name || group.title
|
|
|
|
| 2511 |
const singleEvalViewBuckets = new Map<
|
| 2512 |
string,
|
| 2513 |
{ viewKey: string; label: string; variants: BenchmarkVariant[] }
|
|
|
|
| 2578 |
|
| 2579 |
// Multi-eval family — the view selector chooses among child evals and
|
| 2580 |
// each view exposes that eval's metrics in the bottom tab rail.
|
| 2581 |
+
// Rollup row = this eval IS the family root, i.e. its benchmark id
|
| 2582 |
+
// matches the family id. For multi-benchmark families like HELM,
|
| 2583 |
+
// there is no such eval (HELM has no "helm" benchmark), so rollup
|
| 2584 |
+
// stays null and the children render as siblings.
|
| 2585 |
const rollup =
|
| 2586 |
resolved.find(
|
| 2587 |
(r) =>
|
| 2588 |
+
r.evalEntry.benchmark_id != null &&
|
| 2589 |
+
r.evalEntry.benchmark_id === r.evalEntry.family_id,
|
| 2590 |
) ?? null
|
| 2591 |
const children = rollup ? resolved.filter((r) => r !== rollup) : resolved
|
| 2592 |
const ordered: ResolvedGroup[] = rollup ? [rollup, ...children] : children
|
| 2593 |
|
| 2594 |
const views: PlotboxView[] = ordered
|
| 2595 |
.map((r) => {
|
| 2596 |
+
const rawLabel = r.evalEntry.display_name || r.group.title
|
|
|
|
|
|
|
|
|
|
| 2597 |
const label =
|
| 2598 |
r === rollup ? "Overall" : stripFamilyPrefix(rawLabel, familyName)
|
| 2599 |
const tabs = r.evalEntry.metrics
|
|
|
|
| 2629 |
let hasSlice = false
|
| 2630 |
let hasDistinctLeaves = false
|
| 2631 |
for (const r of children) {
|
| 2632 |
+
// "Distinct leaf" = this child has an identity distinct from the
|
| 2633 |
+
// family root. Two cases:
|
| 2634 |
+
// 1. Non-slice child (e.g. HELM/MMLU under HELM family).
|
| 2635 |
+
// 2. Slice whose parent benchmark is not the family itself
|
| 2636 |
+
// (e.g. HELM/MMLU/anatomy — slice of MMLU under HELM, where
|
| 2637 |
+
// parent="mmlu" ≠ family="helm"). In a singleton family the
|
| 2638 |
+
// slice's parent equals the family and this case collapses
|
| 2639 |
+
// back to "slice", as expected.
|
| 2640 |
+
const evalEntry = r.evalEntry
|
| 2641 |
+
const isDistinctLeaf =
|
| 2642 |
+
evalEntry.is_slice === false ||
|
| 2643 |
+
(evalEntry.parent_benchmark_id != null &&
|
| 2644 |
+
evalEntry.parent_benchmark_id !== evalEntry.family_id)
|
| 2645 |
+
if (isDistinctLeaf) {
|
| 2646 |
hasDistinctLeaves = true
|
| 2647 |
}
|
| 2648 |
if (r.group.variants[0]?.evaluation.benchmark_component_key ?? null) {
|
components/eval-detail.tsx
CHANGED
|
@@ -373,7 +373,8 @@ function splitFlagNote(raw: string): { tag: string | null; body: string } {
|
|
| 373 |
return { tag: match[1].trim(), body: match[2].trim() }
|
| 374 |
}
|
| 375 |
|
| 376 |
-
function formatRawScore(score: number, unit?: string) {
|
|
|
|
| 377 |
const suffix = unit ? ` ${unit}` : ""
|
| 378 |
return `${score.toFixed(2)}${suffix}`
|
| 379 |
}
|
|
@@ -644,8 +645,8 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 644 |
summary.evaluation_name,
|
| 645 |
summary.composite_benchmark_name,
|
| 646 |
summary.composite_benchmark_key,
|
| 647 |
-
summary.
|
| 648 |
-
summary.
|
| 649 |
summary.benchmark_card.benchmark_details?.name,
|
| 650 |
)}
|
| 651 |
/>
|
|
@@ -1317,7 +1318,7 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1317 |
<div className="space-y-3">
|
| 1318 |
<ResearcherReproducibilityCard
|
| 1319 |
modelResult={modelResult}
|
| 1320 |
-
benchmarkKey={summary.benchmark_id ?? summary.
|
| 1321 |
evalName={summary.evaluation_name}
|
| 1322 |
/>
|
| 1323 |
<div className="flex justify-end">
|
|
@@ -2039,7 +2040,7 @@ function MultiMetricLeaderboard({
|
|
| 2039 |
<div className="space-y-3">
|
| 2040 |
<ResearcherReproducibilityCard
|
| 2041 |
modelResult={matchingResult}
|
| 2042 |
-
benchmarkKey={summary.benchmark_id ?? summary.
|
| 2043 |
evalName={summary.evaluation_name}
|
| 2044 |
/>
|
| 2045 |
<div className="flex justify-end">
|
|
|
|
| 373 |
return { tag: match[1].trim(), body: match[2].trim() }
|
| 374 |
}
|
| 375 |
|
| 376 |
+
function formatRawScore(score: number | null | undefined, unit?: string) {
|
| 377 |
+
if (score == null || !Number.isFinite(score)) return "—"
|
| 378 |
const suffix = unit ? ` ${unit}` : ""
|
| 379 |
return `${score.toFixed(2)}${suffix}`
|
| 380 |
}
|
|
|
|
| 645 |
summary.evaluation_name,
|
| 646 |
summary.composite_benchmark_name,
|
| 647 |
summary.composite_benchmark_key,
|
| 648 |
+
summary.family_id,
|
| 649 |
+
summary.benchmark_id,
|
| 650 |
summary.benchmark_card.benchmark_details?.name,
|
| 651 |
)}
|
| 652 |
/>
|
|
|
|
| 1318 |
<div className="space-y-3">
|
| 1319 |
<ResearcherReproducibilityCard
|
| 1320 |
modelResult={modelResult}
|
| 1321 |
+
benchmarkKey={summary.benchmark_id ?? summary.composite_benchmark_key}
|
| 1322 |
evalName={summary.evaluation_name}
|
| 1323 |
/>
|
| 1324 |
<div className="flex justify-end">
|
|
|
|
| 2040 |
<div className="space-y-3">
|
| 2041 |
<ResearcherReproducibilityCard
|
| 2042 |
modelResult={matchingResult}
|
| 2043 |
+
benchmarkKey={summary.benchmark_id ?? summary.composite_benchmark_key}
|
| 2044 |
evalName={summary.evaluation_name}
|
| 2045 |
/>
|
| 2046 |
<div className="flex justify-end">
|
components/family-table.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Fragment, useMemo, useState } from "react"
|
|
| 4 |
import { useRouter } from "next/navigation"
|
| 5 |
import { ArrowUpRight, ChevronDown, ChevronRight } from "lucide-react"
|
| 6 |
|
| 7 |
-
import type { HierarchyBenchmark, HierarchyFamily
|
| 8 |
import type { BenchmarkCard, CategoryType } from "@/lib/benchmark-schema"
|
| 9 |
import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
|
| 10 |
import { humanizeEvaluationId } from "@/lib/utils"
|
|
@@ -118,38 +118,22 @@ function collectLeafEntries(
|
|
| 118 |
})
|
| 119 |
}
|
| 120 |
|
| 121 |
-
if (out.length > 0) return out
|
| 122 |
-
|
| 123 |
-
// ── Fallback: legacy `leaves` shape ────────────────────────────────
|
| 124 |
-
for (const leaf of fam.leaves ?? []) {
|
| 125 |
-
const explicit = leaf.eval_summary_ids ?? []
|
| 126 |
-
const ids =
|
| 127 |
-
explicit.length > 0
|
| 128 |
-
? explicit
|
| 129 |
-
: leaf.key
|
| 130 |
-
? [`${fam.key}_${leaf.key}`, leaf.key]
|
| 131 |
-
: []
|
| 132 |
-
if (ids.length === 0) continue
|
| 133 |
-
const collected = new Set<string>()
|
| 134 |
-
for (const d of leaf.tags?.domains ?? []) collected.add(d.toLowerCase())
|
| 135 |
-
const cardByLeaf = benchmarkCards?.[leaf.key]
|
| 136 |
-
for (const d of cardByLeaf?.benchmark_details?.domains ?? []) collected.add(d.toLowerCase())
|
| 137 |
-
for (const id of ids) {
|
| 138 |
-
const cardById = benchmarkCards?.[id]
|
| 139 |
-
for (const d of cardById?.benchmark_details?.domains ?? []) collected.add(d.toLowerCase())
|
| 140 |
-
}
|
| 141 |
-
out.push({
|
| 142 |
-
id: ids[0],
|
| 143 |
-
leafKey: leaf.key,
|
| 144 |
-
leafName: leaf.display_name || leaf.key,
|
| 145 |
-
evalsCount: leaf.evals_count ?? ids.length,
|
| 146 |
-
domains: Array.from(collected),
|
| 147 |
-
})
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
return out
|
| 151 |
}
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
/**
|
| 154 |
* Pick the eval_summary_id to navigate to when the user clicks the family
|
| 155 |
* row. Returns null when the family has no genuine family-level summary —
|
|
@@ -308,21 +292,17 @@ export function FamilyTable({
|
|
| 308 |
const composites = fam.composites ?? []
|
| 309 |
const standalone = fam.standalone_benchmarks ?? []
|
| 310 |
const benchmarks = fam.benchmarks ?? []
|
| 311 |
-
const leaves: HierarchyLeaf[] = fam.leaves ?? []
|
| 312 |
|
| 313 |
const allBenchmarks = [
|
| 314 |
...standalone,
|
| 315 |
...benchmarks,
|
| 316 |
...composites.flatMap((c) => c.benchmarks ?? []),
|
| 317 |
]
|
| 318 |
-
const metricCount =
|
| 319 |
-
(
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
)
|
| 324 |
-
const benchmarkCount =
|
| 325 |
-
allBenchmarks.length > 0 ? allBenchmarks.length : leaves.length
|
| 326 |
|
| 327 |
const leafEntries = collectLeafEntries(fam, benchmarkCards)
|
| 328 |
const navId = pickFamilyNavId(fam, leafEntries)
|
|
|
|
| 4 |
import { useRouter } from "next/navigation"
|
| 5 |
import { ArrowUpRight, ChevronDown, ChevronRight } from "lucide-react"
|
| 6 |
|
| 7 |
+
import type { HierarchyBenchmark, HierarchyFamily } from "@/lib/backend-artifacts"
|
| 8 |
import type { BenchmarkCard, CategoryType } from "@/lib/benchmark-schema"
|
| 9 |
import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
|
| 10 |
import { humanizeEvaluationId } from "@/lib/utils"
|
|
|
|
| 118 |
})
|
| 119 |
}
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
return out
|
| 122 |
}
|
| 123 |
|
| 124 |
+
/**
|
| 125 |
+
* Resolve the family-level navigation target without exposing the
|
| 126 |
+
* internal `LeafEntry` shape. Returns the eval_summary_id to open when
|
| 127 |
+
* the user clicks the family card / row, or `null` for aggregator
|
| 128 |
+
* families that should expand inline instead of navigating.
|
| 129 |
+
*/
|
| 130 |
+
export function getFamilyNavId(
|
| 131 |
+
fam: HierarchyFamily,
|
| 132 |
+
benchmarkCards?: Record<string, BenchmarkCard>,
|
| 133 |
+
): string | null {
|
| 134 |
+
return pickFamilyNavId(fam, collectLeafEntries(fam, benchmarkCards))
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
/**
|
| 138 |
* Pick the eval_summary_id to navigate to when the user clicks the family
|
| 139 |
* row. Returns null when the family has no genuine family-level summary —
|
|
|
|
| 292 |
const composites = fam.composites ?? []
|
| 293 |
const standalone = fam.standalone_benchmarks ?? []
|
| 294 |
const benchmarks = fam.benchmarks ?? []
|
|
|
|
| 295 |
|
| 296 |
const allBenchmarks = [
|
| 297 |
...standalone,
|
| 298 |
...benchmarks,
|
| 299 |
...composites.flatMap((c) => c.benchmarks ?? []),
|
| 300 |
]
|
| 301 |
+
const metricCount = allBenchmarks.reduce(
|
| 302 |
+
(sum, b) => sum + (b.metrics?.length ?? 0),
|
| 303 |
+
0,
|
| 304 |
+
)
|
| 305 |
+
const benchmarkCount = allBenchmarks.length
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
const leafEntries = collectLeafEntries(fam, benchmarkCards)
|
| 308 |
const navId = pickFamilyNavId(fam, leafEntries)
|
components/policy-overview.tsx
CHANGED
|
@@ -59,7 +59,7 @@ export function PolicyOverview({ summary }: PolicyOverviewProps) {
|
|
| 59 |
const cardNameNorm = normalizeId(cardName)
|
| 60 |
const evalIdentifiers = [
|
| 61 |
summary.evaluation_name,
|
| 62 |
-
summary.
|
| 63 |
summary.composite_benchmark_key,
|
| 64 |
summary.composite_benchmark_name,
|
| 65 |
summary.canonical_display_name,
|
|
@@ -180,16 +180,16 @@ export function PolicyOverview({ summary }: PolicyOverviewProps) {
|
|
| 180 |
summary.evaluation_name,
|
| 181 |
summary.composite_benchmark_name,
|
| 182 |
summary.composite_benchmark_key,
|
| 183 |
-
summary.
|
| 184 |
-
summary.
|
| 185 |
card?.benchmark_details?.name,
|
| 186 |
),
|
| 187 |
[
|
| 188 |
summary.evaluation_name,
|
| 189 |
summary.composite_benchmark_name,
|
| 190 |
summary.composite_benchmark_key,
|
| 191 |
-
summary.
|
| 192 |
-
summary.
|
| 193 |
card?.benchmark_details?.name,
|
| 194 |
],
|
| 195 |
)
|
|
|
|
| 59 |
const cardNameNorm = normalizeId(cardName)
|
| 60 |
const evalIdentifiers = [
|
| 61 |
summary.evaluation_name,
|
| 62 |
+
summary.benchmark_id,
|
| 63 |
summary.composite_benchmark_key,
|
| 64 |
summary.composite_benchmark_name,
|
| 65 |
summary.canonical_display_name,
|
|
|
|
| 180 |
summary.evaluation_name,
|
| 181 |
summary.composite_benchmark_name,
|
| 182 |
summary.composite_benchmark_key,
|
| 183 |
+
summary.family_id,
|
| 184 |
+
summary.benchmark_id,
|
| 185 |
card?.benchmark_details?.name,
|
| 186 |
),
|
| 187 |
[
|
| 188 |
summary.evaluation_name,
|
| 189 |
summary.composite_benchmark_name,
|
| 190 |
summary.composite_benchmark_key,
|
| 191 |
+
summary.family_id,
|
| 192 |
+
summary.benchmark_id,
|
| 193 |
card?.benchmark_details?.name,
|
| 194 |
],
|
| 195 |
)
|
components/signals/corpus-signals-strip.tsx
CHANGED
|
@@ -87,7 +87,7 @@ export function CorpusSignalsStrip({
|
|
| 87 |
statValue={pctNum(multiSourceRate)}
|
| 88 |
statUnit="%"
|
| 89 |
headline="of reported score triples have reports from more than one party."
|
| 90 |
-
detail={`${formatPct(tpShare)} third-party, ${formatPct(fpShare)} first-party of ${totalReports.toLocaleString()} triples.`}
|
| 91 |
asks="Who reported this score, and have others reproduced it?"
|
| 92 |
/>
|
| 93 |
<SignalTile
|
|
|
|
| 87 |
statValue={pctNum(multiSourceRate)}
|
| 88 |
statUnit="%"
|
| 89 |
headline="of reported score triples have reports from more than one party."
|
| 90 |
+
detail={`${formatPct(tpShare)} third-party, ${formatPct(fpShare)} first-party of ${totalReports.toLocaleString()} unique triples.`}
|
| 91 |
asks="Who reported this score, and have others reproduced it?"
|
| 92 |
/>
|
| 93 |
<SignalTile
|
lib/backend-artifacts.ts
CHANGED
|
@@ -241,6 +241,24 @@ export interface ComparabilityCorpusBlock {
|
|
| 241 |
groups_with_cross_party_check: number
|
| 242 |
}
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
export interface HierarchyTags {
|
| 245 |
domains: string[]
|
| 246 |
languages: string[]
|
|
@@ -250,109 +268,108 @@ export interface HierarchyTags {
|
|
| 250 |
export interface HierarchyMetric {
|
| 251 |
key: string
|
| 252 |
display_name: string
|
|
|
|
|
|
|
| 253 |
sources?: string[]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
}
|
| 255 |
|
| 256 |
export interface HierarchySlice {
|
| 257 |
key: string
|
| 258 |
display_name: string
|
| 259 |
metrics: HierarchyMetric[]
|
| 260 |
-
/**
|
| 261 |
-
*
|
| 262 |
is_bare_stem?: boolean
|
| 263 |
}
|
| 264 |
|
| 265 |
export interface HierarchyBenchmark extends SignalSummaries {
|
| 266 |
key: string
|
| 267 |
display_name: string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
has_card: boolean
|
| 269 |
tags: HierarchyTags
|
| 270 |
slices: HierarchySlice[]
|
| 271 |
metrics: HierarchyMetric[]
|
| 272 |
summary_eval_ids?: string[]
|
| 273 |
-
/** New post-cutover: family slug (defaults to benchmark key for singletons). */
|
| 274 |
-
family_id?: string
|
| 275 |
-
/** New post-cutover: TRUE when this row is a slice of a root benchmark. */
|
| 276 |
-
is_slice?: boolean
|
| 277 |
}
|
| 278 |
|
| 279 |
export interface HierarchyComposite extends SignalSummaries {
|
| 280 |
key: string
|
| 281 |
display_name: string
|
| 282 |
-
has_card?: boolean
|
| 283 |
category: string
|
| 284 |
tags: HierarchyTags
|
| 285 |
benchmarks: HierarchyBenchmark[]
|
| 286 |
-
summary_eval_ids?: string[]
|
| 287 |
-
/** New top-level shape: total triples in the composite. */
|
| 288 |
evals_count?: number
|
|
|
|
|
|
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
-
export interface
|
| 292 |
key: string
|
| 293 |
display_name: string
|
| 294 |
category: string
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
| 300 |
|
| 301 |
-
export interface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
key: string
|
| 303 |
display_name: string
|
| 304 |
-
|
| 305 |
-
category: string
|
| 306 |
-
tags?: Partial<HierarchyTags>
|
| 307 |
-
evals_count?: number
|
| 308 |
-
eval_summary_ids?: string[]
|
| 309 |
-
// Legacy nested shape (composites + standalone benchmarks)
|
| 310 |
-
standalone_benchmarks?: HierarchyBenchmark[]
|
| 311 |
-
composites?: HierarchyComposite[]
|
| 312 |
-
benchmarks?: HierarchyBenchmark[]
|
| 313 |
-
slices?: HierarchySlice[]
|
| 314 |
-
metrics?: HierarchyMetric[]
|
| 315 |
-
// Newer 2-level shape (family → leaf)
|
| 316 |
-
leaves?: HierarchyLeaf[]
|
| 317 |
}
|
| 318 |
|
| 319 |
export interface EvalHierarchyStats {
|
| 320 |
family_count: number
|
| 321 |
composite_count: number
|
| 322 |
-
|
| 323 |
-
* family/slice taxonomy refactor — kept optional so the adapter
|
| 324 |
-
* can synthesise it for the existing homepage stats strip. */
|
| 325 |
-
standalone_benchmark_count?: number
|
| 326 |
-
/** Same as above. */
|
| 327 |
-
single_benchmark_count?: number
|
| 328 |
-
/** New post-cutover field: total distinct (composite, benchmark) rows
|
| 329 |
-
* in the benchmarks dim. */
|
| 330 |
-
benchmark_count?: number
|
| 331 |
slice_count: number
|
| 332 |
metric_count: number
|
| 333 |
metric_rows_scanned: number
|
| 334 |
}
|
| 335 |
|
| 336 |
-
/** Lightweight family-lookup index entry from the new top-level
|
| 337 |
-
* `families[]` array (composite/family/slice taxonomy). One per
|
| 338 |
-
* family_id with the list of member benchmark keys — no nested
|
| 339 |
-
* composites, no slice payload. The legacy `HierarchyFamily` shape
|
| 340 |
-
* (with nested `composites[]` / `standalone_benchmarks[]`) is
|
| 341 |
-
* synthesised by the adapter for backward compat. */
|
| 342 |
-
export interface HierarchyFamilyIndex {
|
| 343 |
-
key: string
|
| 344 |
-
display_name: string
|
| 345 |
-
member_benchmark_keys: string[]
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
export interface EvalHierarchy {
|
|
|
|
|
|
|
|
|
|
| 349 |
stats?: EvalHierarchyStats
|
| 350 |
families: HierarchyFamily[]
|
| 351 |
-
|
| 352 |
-
composites?: HierarchyComposite[]
|
| 353 |
-
/** New post-cutover flat lookup. The adapter promotes this onto
|
| 354 |
-
* per-family records as it builds the legacy shape. */
|
| 355 |
-
family_index?: HierarchyFamilyIndex[]
|
| 356 |
}
|
| 357 |
|
| 358 |
// ---------------------------------------------------------------------------
|
|
@@ -407,14 +424,15 @@ export interface ComparisonMetricEntry {
|
|
| 407 |
|
| 408 |
export interface ComparisonEvalEntry {
|
| 409 |
eval_summary_id: string
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
display_name: string | null
|
| 417 |
category: string
|
|
|
|
| 418 |
is_summary_score: boolean
|
| 419 |
summary_score_for: string | null
|
| 420 |
summary_eval_ids: string[]
|
|
|
|
| 241 |
groups_with_cross_party_check: number
|
| 242 |
}
|
| 243 |
|
| 244 |
+
// ---------------------------------------------------------------------------
|
| 245 |
+
// Hierarchy types (v3 — family-rooted tree).
|
| 246 |
+
//
|
| 247 |
+
// The producer emits this shape via eval_card_backend's
|
| 248 |
+
// `write_hierarchy()` after the Step 3 reshape. See
|
| 249 |
+
// /Users/jchim/projects/evaleval/notes/hierarchy-alignment.md §5.1
|
| 250 |
+
// for the canonical spec.
|
| 251 |
+
//
|
| 252 |
+
// Top level: `families[]` is the rich entity. Composites nest under
|
| 253 |
+
// families[].composites[]. `benchmark_index[]` cross-links a canonical
|
| 254 |
+
// benchmark that appears in multiple families.
|
| 255 |
+
//
|
| 256 |
+
// Each family chooses ONE of three layouts:
|
| 257 |
+
// - standalone_benchmarks: single-benchmark family.
|
| 258 |
+
// - benchmarks (flat): multiple benchmarks, no composite layer.
|
| 259 |
+
// - composites: multi-composite family (HELM has 7).
|
| 260 |
+
// ---------------------------------------------------------------------------
|
| 261 |
+
|
| 262 |
export interface HierarchyTags {
|
| 263 |
domains: string[]
|
| 264 |
languages: string[]
|
|
|
|
| 268 |
export interface HierarchyMetric {
|
| 269 |
key: string
|
| 270 |
display_name: string
|
| 271 |
+
/** Producer-supplied list of organisations whose results back this
|
| 272 |
+
* metric. Empty when source attribution wasn't recoverable. */
|
| 273 |
sources?: string[]
|
| 274 |
+
/** Per spec §5.1 — true when this is the benchmark's primary metric
|
| 275 |
+
* (matches `primary_metric_key`). */
|
| 276 |
+
is_primary?: boolean
|
| 277 |
+
/** Distinct model count contributing to this metric — drives
|
| 278 |
+
* primary-metric tie-break. */
|
| 279 |
+
models_count?: number
|
| 280 |
}
|
| 281 |
|
| 282 |
export interface HierarchySlice {
|
| 283 |
key: string
|
| 284 |
display_name: string
|
| 285 |
metrics: HierarchyMetric[]
|
| 286 |
+
/** Marks the bare-stem "Overall" slice (e.g. `gaia` inside the
|
| 287 |
+
* `gaia` benchmark). Frontend labels such a row "Overall". */
|
| 288 |
is_bare_stem?: boolean
|
| 289 |
}
|
| 290 |
|
| 291 |
export interface HierarchyBenchmark extends SignalSummaries {
|
| 292 |
key: string
|
| 293 |
display_name: string
|
| 294 |
+
family_id: string
|
| 295 |
+
is_slice: boolean
|
| 296 |
+
/** True when this row IS the family/composite root (canonical_id
|
| 297 |
+
* matches the family or composite key). For a singleton family,
|
| 298 |
+
* the sole benchmark is overall. For multi-bench families with
|
| 299 |
+
* no head benchmark of the same name (HAL, BFCL with no `bfcl`
|
| 300 |
+
* benchmark), all are False. */
|
| 301 |
+
is_overall: boolean
|
| 302 |
+
/** True for the benchmark within its family that's the headline
|
| 303 |
+
* reading. Selected via FAMILY_PRIMARY_OVERRIDE → is_overall →
|
| 304 |
+
* alphabetical (see _mark_family_primary_benchmark in producer). */
|
| 305 |
+
is_primary?: boolean
|
| 306 |
+
/** Metric key whose primary metric should be displayed as the
|
| 307 |
+
* benchmark's headline number. Null when the benchmark has no
|
| 308 |
+
* metrics. */
|
| 309 |
+
primary_metric_key?: string | null
|
| 310 |
has_card: boolean
|
| 311 |
tags: HierarchyTags
|
| 312 |
slices: HierarchySlice[]
|
| 313 |
metrics: HierarchyMetric[]
|
| 314 |
summary_eval_ids?: string[]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
}
|
| 316 |
|
| 317 |
export interface HierarchyComposite extends SignalSummaries {
|
| 318 |
key: string
|
| 319 |
display_name: string
|
|
|
|
| 320 |
category: string
|
| 321 |
tags: HierarchyTags
|
| 322 |
benchmarks: HierarchyBenchmark[]
|
|
|
|
|
|
|
| 323 |
evals_count?: number
|
| 324 |
+
summary_eval_ids?: string[]
|
| 325 |
+
/** True for the headline composite within a multi-composite family. */
|
| 326 |
+
is_primary?: boolean
|
| 327 |
}
|
| 328 |
|
| 329 |
+
export interface HierarchyFamily extends SignalSummaries {
|
| 330 |
key: string
|
| 331 |
display_name: string
|
| 332 |
category: string
|
| 333 |
+
tags: HierarchyTags
|
| 334 |
+
evals_count: number
|
| 335 |
+
eval_summary_ids: string[]
|
| 336 |
+
/** Exactly ONE of the three layout fields below is present. */
|
| 337 |
+
standalone_benchmarks?: HierarchyBenchmark[]
|
| 338 |
+
benchmarks?: HierarchyBenchmark[]
|
| 339 |
+
composites?: HierarchyComposite[]
|
| 340 |
}
|
| 341 |
|
| 342 |
+
export interface BenchmarkIndexAppearance {
|
| 343 |
+
family_key: string
|
| 344 |
+
benchmark_key: string
|
| 345 |
+
eval_summary_ids: string[]
|
| 346 |
+
/** True when the family this appearance is under is the benchmark's
|
| 347 |
+
* natural "home" family (family_key === benchmark_key). */
|
| 348 |
+
is_canonical_home: boolean
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
export interface BenchmarkIndexEntry {
|
| 352 |
key: string
|
| 353 |
display_name: string
|
| 354 |
+
appearances: BenchmarkIndexAppearance[]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
}
|
| 356 |
|
| 357 |
export interface EvalHierarchyStats {
|
| 358 |
family_count: number
|
| 359 |
composite_count: number
|
| 360 |
+
benchmark_count: number
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
slice_count: number
|
| 362 |
metric_count: number
|
| 363 |
metric_rows_scanned: number
|
| 364 |
}
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
export interface EvalHierarchy {
|
| 367 |
+
/** Schema marker: "v3.hierarchy.1". Older snapshots lack this. */
|
| 368 |
+
schema_version?: string
|
| 369 |
+
generated_at?: string
|
| 370 |
stats?: EvalHierarchyStats
|
| 371 |
families: HierarchyFamily[]
|
| 372 |
+
benchmark_index?: BenchmarkIndexEntry[]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
}
|
| 374 |
|
| 375 |
// ---------------------------------------------------------------------------
|
|
|
|
| 424 |
|
| 425 |
export interface ComparisonEvalEntry {
|
| 426 |
eval_summary_id: string
|
| 427 |
+
benchmark_id: string | null
|
| 428 |
+
family_id: string | null
|
| 429 |
+
family_display_name: string | null
|
| 430 |
+
composite_slug: string | null
|
| 431 |
+
composite_display_name: string | null
|
| 432 |
+
parent_benchmark_id: string | null
|
| 433 |
display_name: string | null
|
| 434 |
category: string
|
| 435 |
+
is_slice: boolean
|
| 436 |
is_summary_score: boolean
|
| 437 |
summary_score_for: string | null
|
| 438 |
summary_eval_ids: string[]
|
lib/benchmark-schema.ts
CHANGED
|
@@ -10,16 +10,22 @@ export interface BenchmarkEvaluation {
|
|
| 10 |
eval_summary_id?: string
|
| 11 |
evaluation_id: string
|
| 12 |
retrieved_timestamp: string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
benchmark?: string
|
| 14 |
display_name?: string
|
| 15 |
canonical_display_name?: string
|
| 16 |
category?: CategoryType
|
| 17 |
-
|
| 18 |
benchmark_family_name?: string
|
| 19 |
-
|
| 20 |
benchmark_parent_name?: string
|
| 21 |
-
benchmark_leaf_key?: string
|
| 22 |
benchmark_leaf_name?: string
|
|
|
|
| 23 |
benchmark_component_key?: string | null
|
| 24 |
benchmark_component_name?: string | null
|
| 25 |
is_summary_score?: boolean
|
|
|
|
| 10 |
eval_summary_id?: string
|
| 11 |
evaluation_id: string
|
| 12 |
retrieved_timestamp: string
|
| 13 |
+
/** Legacy benchmark-name-or-slug field. The view-data layer
|
| 14 |
+
* (lib/view-data.ts) populates this from eval_evaluation_name with a
|
| 15 |
+
* benchmark_id fallback, so it works as a stable name source for
|
| 16 |
+
* badges and grouping when nothing better is on-hand. New callers
|
| 17 |
+
* should prefer `display_name` / `canonical_display_name`; this
|
| 18 |
+
* field is kept for the surfaces that aren't yet migrated. */
|
| 19 |
benchmark?: string
|
| 20 |
display_name?: string
|
| 21 |
canonical_display_name?: string
|
| 22 |
category?: CategoryType
|
| 23 |
+
family_id?: string
|
| 24 |
benchmark_family_name?: string
|
| 25 |
+
parent_benchmark_id?: string
|
| 26 |
benchmark_parent_name?: string
|
|
|
|
| 27 |
benchmark_leaf_name?: string
|
| 28 |
+
is_slice?: boolean
|
| 29 |
benchmark_component_key?: string | null
|
| 30 |
benchmark_component_name?: string | null
|
| 31 |
is_summary_score?: boolean
|
lib/eval-processing.ts
CHANGED
|
@@ -176,22 +176,20 @@ export interface BenchmarkEvalSummary extends SignalSummaries {
|
|
| 176 |
/** Canonical benchmark id (the registry-resolved benchmark). Drives
|
| 177 |
* benchmark-card lookups regardless of slice/composite axis. */
|
| 178 |
benchmark_id?: string
|
| 179 |
-
/** Benchmark family grouping key — curated multi-benchmark family
|
| 180 |
-
* slug (e.g. "mmlu"), defaults to benchmark id for singletons. */
|
| 181 |
-
benchmark_family_key?: string
|
| 182 |
/** Family display name. */
|
| 183 |
benchmark_family_name?: string
|
| 184 |
-
/** Leaf benchmark key — populated when this row is a slice of a
|
| 185 |
-
* parent benchmark; null for non-slice rows. */
|
| 186 |
-
benchmark_leaf_key?: string
|
| 187 |
/** Composite (leaderboard) slug — e.g. "wasp", "helm-classic". */
|
| 188 |
composite_slug?: string
|
| 189 |
/** Composite display name — e.g. "WASP", "HELM Classic". */
|
| 190 |
composite_display_name?: string
|
| 191 |
-
/**
|
|
|
|
| 192 |
family_id?: string
|
| 193 |
/** Family display, post-cutover canonical name. */
|
| 194 |
family_display_name?: string
|
|
|
|
|
|
|
|
|
|
| 195 |
/** True when this row is a within-benchmark slice cut. */
|
| 196 |
is_slice?: boolean
|
| 197 |
/** Source dataset metadata from the pipeline */
|
|
|
|
| 176 |
/** Canonical benchmark id (the registry-resolved benchmark). Drives
|
| 177 |
* benchmark-card lookups regardless of slice/composite axis. */
|
| 178 |
benchmark_id?: string
|
|
|
|
|
|
|
|
|
|
| 179 |
/** Family display name. */
|
| 180 |
benchmark_family_name?: string
|
|
|
|
|
|
|
|
|
|
| 181 |
/** Composite (leaderboard) slug — e.g. "wasp", "helm-classic". */
|
| 182 |
composite_slug?: string
|
| 183 |
/** Composite display name — e.g. "WASP", "HELM Classic". */
|
| 184 |
composite_display_name?: string
|
| 185 |
+
/** Curated multi-benchmark family slug (e.g. "mmlu"), defaults to
|
| 186 |
+
* benchmark id for singletons. */
|
| 187 |
family_id?: string
|
| 188 |
/** Family display, post-cutover canonical name. */
|
| 189 |
family_display_name?: string
|
| 190 |
+
/** Parent benchmark id — populated when this row is a slice of a
|
| 191 |
+
* root benchmark; null for non-slice rows. */
|
| 192 |
+
parent_benchmark_id?: string
|
| 193 |
/** True when this row is a within-benchmark slice cut. */
|
| 194 |
is_slice?: boolean
|
| 195 |
/** Source dataset metadata from the pipeline */
|
lib/hf-data.ts
CHANGED
|
@@ -932,386 +932,35 @@ export async function fetchEvalHierarchy(): Promise<EvalHierarchy> {
|
|
| 932 |
* back under per-family records, with the composite slug as the
|
| 933 |
* family key when no curated multi-benchmark family applies.
|
| 934 |
*/
|
| 935 |
-
export function adaptEvalHierarchy(raw: EvalHierarchy): EvalHierarchy {
|
| 936 |
-
const newShape = Array.isArray(raw.composites) && raw.composites.length > 0
|
| 937 |
-
if (newShape) {
|
| 938 |
-
return adaptCompositeShape(raw)
|
| 939 |
-
}
|
| 940 |
-
|
| 941 |
-
const families = (raw.families ?? []).map((family) => {
|
| 942 |
-
const hasLegacyTree =
|
| 943 |
-
(family.composites && family.composites.length > 0) ||
|
| 944 |
-
(family.standalone_benchmarks && family.standalone_benchmarks.length > 0) ||
|
| 945 |
-
(family.benchmarks && family.benchmarks.length > 0)
|
| 946 |
-
|
| 947 |
-
if (hasLegacyTree) {
|
| 948 |
-
return family
|
| 949 |
-
}
|
| 950 |
-
|
| 951 |
-
const leaves = family.leaves ?? []
|
| 952 |
-
if (leaves.length === 0) {
|
| 953 |
-
return family
|
| 954 |
-
}
|
| 955 |
-
|
| 956 |
-
const standalone = leaves.map((leaf) => ({
|
| 957 |
-
key: leaf.key,
|
| 958 |
-
display_name: leaf.display_name,
|
| 959 |
-
has_card: leaf.has_card ?? false,
|
| 960 |
-
tags: {
|
| 961 |
-
domains: leaf.tags?.domains ?? [],
|
| 962 |
-
languages: leaf.tags?.languages ?? [],
|
| 963 |
-
tasks: leaf.tags?.tasks ?? [],
|
| 964 |
-
},
|
| 965 |
-
slices: [],
|
| 966 |
-
metrics: [],
|
| 967 |
-
reproducibility_summary: leaf.reproducibility_summary,
|
| 968 |
-
provenance_summary: leaf.provenance_summary,
|
| 969 |
-
comparability_summary: leaf.comparability_summary,
|
| 970 |
-
summary_eval_ids: leaf.eval_summary_ids,
|
| 971 |
-
}))
|
| 972 |
-
|
| 973 |
-
return {
|
| 974 |
-
...family,
|
| 975 |
-
tags: {
|
| 976 |
-
domains: family.tags?.domains ?? [],
|
| 977 |
-
languages: family.tags?.languages ?? [],
|
| 978 |
-
tasks: family.tags?.tasks ?? [],
|
| 979 |
-
},
|
| 980 |
-
standalone_benchmarks: standalone,
|
| 981 |
-
}
|
| 982 |
-
})
|
| 983 |
-
|
| 984 |
-
if (raw.stats) {
|
| 985 |
-
return { ...raw, families }
|
| 986 |
-
}
|
| 987 |
-
|
| 988 |
-
let composite_count = 0
|
| 989 |
-
let standalone_benchmark_count = 0
|
| 990 |
-
let single_benchmark_count = 0
|
| 991 |
-
let slice_count = 0
|
| 992 |
-
let metric_count = 0
|
| 993 |
-
|
| 994 |
-
for (const family of families) {
|
| 995 |
-
composite_count += family.composites?.length ?? 0
|
| 996 |
-
const standalone = family.standalone_benchmarks ?? []
|
| 997 |
-
standalone_benchmark_count += standalone.length
|
| 998 |
-
if ((family.composites?.length ?? 0) === 0 && standalone.length === 1) {
|
| 999 |
-
single_benchmark_count += 1
|
| 1000 |
-
}
|
| 1001 |
-
for (const composite of family.composites ?? []) {
|
| 1002 |
-
for (const benchmark of composite.benchmarks ?? []) {
|
| 1003 |
-
slice_count += benchmark.slices?.length ?? 0
|
| 1004 |
-
metric_count += benchmark.metrics?.length ?? 0
|
| 1005 |
-
}
|
| 1006 |
-
}
|
| 1007 |
-
for (const benchmark of standalone) {
|
| 1008 |
-
slice_count += benchmark.slices?.length ?? 0
|
| 1009 |
-
metric_count += benchmark.metrics?.length ?? 0
|
| 1010 |
-
}
|
| 1011 |
-
}
|
| 1012 |
-
|
| 1013 |
-
return {
|
| 1014 |
-
...raw,
|
| 1015 |
-
families,
|
| 1016 |
-
stats: {
|
| 1017 |
-
family_count: families.length,
|
| 1018 |
-
composite_count,
|
| 1019 |
-
standalone_benchmark_count,
|
| 1020 |
-
single_benchmark_count,
|
| 1021 |
-
slice_count,
|
| 1022 |
-
metric_count,
|
| 1023 |
-
metric_rows_scanned: 0,
|
| 1024 |
-
},
|
| 1025 |
-
}
|
| 1026 |
-
}
|
| 1027 |
-
|
| 1028 |
/**
|
| 1029 |
-
*
|
| 1030 |
-
*
|
| 1031 |
-
*
|
| 1032 |
-
*
|
| 1033 |
*
|
| 1034 |
-
*
|
| 1035 |
-
*
|
| 1036 |
-
*
|
| 1037 |
-
*
|
| 1038 |
-
*
|
| 1039 |
-
*
|
| 1040 |
-
* benchmark.slices[] carry through unchanged.
|
| 1041 |
*
|
| 1042 |
-
*
|
| 1043 |
-
*
|
| 1044 |
-
*
|
| 1045 |
-
*
|
| 1046 |
*/
|
| 1047 |
-
function
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
if (!fam || typeof fam !== "object") continue
|
| 1051 |
-
const f = fam as unknown as { key?: string; display_name?: string; member_benchmark_keys?: string[] }
|
| 1052 |
-
if (!f.key) continue
|
| 1053 |
-
familyIndex.set(f.key, {
|
| 1054 |
-
display_name: f.display_name ?? f.key,
|
| 1055 |
-
member_keys: f.member_benchmark_keys ?? [],
|
| 1056 |
-
})
|
| 1057 |
-
}
|
| 1058 |
-
|
| 1059 |
-
type LegacyBenchmark = HierarchyBenchmark & { _composite_slug?: string }
|
| 1060 |
-
|
| 1061 |
-
// Set of benchmark keys claimed by curated multi-benchmark families
|
| 1062 |
-
// (families.yaml), so we don't redundantly bucket them under their
|
| 1063 |
-
// composite below. Crucially this only counts MULTI-member families
|
| 1064 |
-
// (≥2 benchmarks) — every benchmark also gets a synthetic singleton
|
| 1065 |
-
// family from the backend's `_synthesise_singleton_families` pass,
|
| 1066 |
-
// and treating those as curated would short-circuit the
|
| 1067 |
-
// composite-implicit grouping (Pass B) and scatter every leaderboard
|
| 1068 |
-
// benchmark into its own row.
|
| 1069 |
-
const curatedMembers = new Set<string>()
|
| 1070 |
-
for (const fam of familyIndex.values()) {
|
| 1071 |
-
if (fam.member_keys.length < 2) continue
|
| 1072 |
-
for (const k of fam.member_keys) curatedMembers.add(k)
|
| 1073 |
-
}
|
| 1074 |
-
|
| 1075 |
-
// Two grouping passes, in priority order:
|
| 1076 |
-
//
|
| 1077 |
-
// (1) Curated families from families.yaml — bucketed by `family_id`.
|
| 1078 |
-
// Drives the MMLU family, BFCL family, JudgeBench family rows.
|
| 1079 |
-
//
|
| 1080 |
-
// (2) Composite-implicit groupings — for benchmarks NOT in any
|
| 1081 |
-
// curated family, group by their composite_slug if the
|
| 1082 |
-
// composite has ≥2 such benchmarks. This restores the legacy
|
| 1083 |
-
// "HELM Classic / HELM Lite / HELM Safety" family rows that
|
| 1084 |
-
// would otherwise scatter across one singleton family per
|
| 1085 |
-
// leaf benchmark, hiding the leaderboard structure.
|
| 1086 |
-
//
|
| 1087 |
-
// (3) Anything still ungrouped lands as a singleton standalone.
|
| 1088 |
-
type Bucket = {
|
| 1089 |
-
key: string
|
| 1090 |
-
display: string
|
| 1091 |
-
benches: LegacyBenchmark[]
|
| 1092 |
-
/** Curated family vs synthesised-from-composite vs singleton. Drives
|
| 1093 |
-
* whether the legacy `composites[]` slot or `standalone_benchmarks[]`
|
| 1094 |
-
* is populated. */
|
| 1095 |
-
kind: "curated" | "composite" | "singleton"
|
| 1096 |
-
}
|
| 1097 |
-
const buckets = new Map<string, Bucket>()
|
| 1098 |
-
|
| 1099 |
-
const toLegacyBenchmark = (
|
| 1100 |
-
bench: HierarchyComposite["benchmarks"][number],
|
| 1101 |
-
composite: HierarchyComposite,
|
| 1102 |
-
): LegacyBenchmark => ({
|
| 1103 |
-
key: bench.key,
|
| 1104 |
-
display_name: bench.display_name,
|
| 1105 |
-
has_card: bench.has_card ?? false,
|
| 1106 |
-
tags: {
|
| 1107 |
-
domains: bench.tags?.domains ?? [],
|
| 1108 |
-
languages: bench.tags?.languages ?? [],
|
| 1109 |
-
tasks: bench.tags?.tasks ?? [],
|
| 1110 |
-
},
|
| 1111 |
-
slices: bench.slices ?? [],
|
| 1112 |
-
metrics: bench.metrics ?? [],
|
| 1113 |
-
summary_eval_ids: bench.summary_eval_ids,
|
| 1114 |
-
reproducibility_summary: bench.reproducibility_summary,
|
| 1115 |
-
provenance_summary: bench.provenance_summary,
|
| 1116 |
-
comparability_summary: bench.comparability_summary,
|
| 1117 |
-
family_id: bench.family_id ?? bench.key,
|
| 1118 |
-
is_slice: bench.is_slice ?? false,
|
| 1119 |
-
_composite_slug: composite.key,
|
| 1120 |
-
})
|
| 1121 |
-
|
| 1122 |
-
// Pass A: curated families.
|
| 1123 |
-
for (const composite of raw.composites ?? []) {
|
| 1124 |
-
for (const bench of composite.benchmarks ?? []) {
|
| 1125 |
-
const familyId = bench.family_id ?? bench.key
|
| 1126 |
-
if (!curatedMembers.has(bench.key)) continue
|
| 1127 |
-
const entry = familyIndex.get(familyId)
|
| 1128 |
-
if (!entry) continue
|
| 1129 |
-
const bucketKey = `family:${familyId}`
|
| 1130 |
-
if (!buckets.has(bucketKey)) {
|
| 1131 |
-
buckets.set(bucketKey, {
|
| 1132 |
-
key: familyId,
|
| 1133 |
-
display: entry.display_name,
|
| 1134 |
-
benches: [],
|
| 1135 |
-
kind: "curated",
|
| 1136 |
-
})
|
| 1137 |
-
}
|
| 1138 |
-
buckets.get(bucketKey)!.benches.push(toLegacyBenchmark(bench, composite))
|
| 1139 |
-
}
|
| 1140 |
-
}
|
| 1141 |
-
|
| 1142 |
-
// Pass B: composite-implicit groupings. Per composite, count
|
| 1143 |
-
// distinct non-curated benchmark keys; if ≥2, group them under the
|
| 1144 |
-
// composite slug; otherwise fall through to the singleton pass.
|
| 1145 |
-
for (const composite of raw.composites ?? []) {
|
| 1146 |
-
const eligibleBenches = (composite.benchmarks ?? [])
|
| 1147 |
-
.filter((b) => !curatedMembers.has(b.key))
|
| 1148 |
-
const distinctKeys = new Set(eligibleBenches.map((b) => b.key))
|
| 1149 |
-
if (distinctKeys.size < 2) continue
|
| 1150 |
-
const bucketKey = `composite:${composite.key}`
|
| 1151 |
-
if (!buckets.has(bucketKey)) {
|
| 1152 |
-
buckets.set(bucketKey, {
|
| 1153 |
-
key: composite.key,
|
| 1154 |
-
display: composite.display_name,
|
| 1155 |
-
benches: [],
|
| 1156 |
-
kind: "composite",
|
| 1157 |
-
})
|
| 1158 |
-
}
|
| 1159 |
-
for (const bench of eligibleBenches) {
|
| 1160 |
-
buckets.get(bucketKey)!.benches.push(toLegacyBenchmark(bench, composite))
|
| 1161 |
-
}
|
| 1162 |
-
}
|
| 1163 |
-
|
| 1164 |
-
// Pass C: singletons (benchmarks not in a curated family and whose
|
| 1165 |
-
// composite carries only this benchmark). Bucketed by benchmark key.
|
| 1166 |
-
for (const composite of raw.composites ?? []) {
|
| 1167 |
-
const eligibleBenches = (composite.benchmarks ?? [])
|
| 1168 |
-
.filter((b) => !curatedMembers.has(b.key))
|
| 1169 |
-
const distinctKeys = new Set(eligibleBenches.map((b) => b.key))
|
| 1170 |
-
if (distinctKeys.size >= 2) continue
|
| 1171 |
-
for (const bench of eligibleBenches) {
|
| 1172 |
-
const bucketKey = `bench:${bench.key}`
|
| 1173 |
-
if (!buckets.has(bucketKey)) {
|
| 1174 |
-
buckets.set(bucketKey, {
|
| 1175 |
-
key: bench.key,
|
| 1176 |
-
display: bench.display_name,
|
| 1177 |
-
benches: [],
|
| 1178 |
-
kind: "singleton",
|
| 1179 |
-
})
|
| 1180 |
-
}
|
| 1181 |
-
buckets.get(bucketKey)!.benches.push(toLegacyBenchmark(bench, composite))
|
| 1182 |
-
}
|
| 1183 |
-
}
|
| 1184 |
-
|
| 1185 |
-
// Synthesise legacy family records.
|
| 1186 |
-
const families: HierarchyFamily[] = []
|
| 1187 |
-
let standalone_benchmark_count = 0
|
| 1188 |
-
let single_benchmark_count = 0
|
| 1189 |
-
let synthesised_composite_count = 0
|
| 1190 |
-
|
| 1191 |
-
// Sort by display name for stable rendering. Curated families first,
|
| 1192 |
-
// then composite-implicit groupings, then singletons.
|
| 1193 |
-
const sortedBuckets = Array.from(buckets.values()).sort((a, b) => {
|
| 1194 |
-
const kindOrder = { curated: 0, composite: 1, singleton: 2 } as const
|
| 1195 |
-
if (kindOrder[a.kind] !== kindOrder[b.kind]) {
|
| 1196 |
-
return kindOrder[a.kind] - kindOrder[b.kind]
|
| 1197 |
-
}
|
| 1198 |
-
return a.key.localeCompare(b.key)
|
| 1199 |
-
})
|
| 1200 |
-
|
| 1201 |
-
for (const bucket of sortedBuckets) {
|
| 1202 |
-
const benches = bucket.benches
|
| 1203 |
-
if (benches.length === 0) continue
|
| 1204 |
-
const display = bucket.display ?? bucket.key
|
| 1205 |
-
const distinctBenchmarkKeys = new Set(benches.map((b) => b.key))
|
| 1206 |
-
|
| 1207 |
-
const tagDomains = new Set<string>()
|
| 1208 |
-
const tagLanguages = new Set<string>()
|
| 1209 |
-
const tagTasks = new Set<string>()
|
| 1210 |
-
for (const b of benches) {
|
| 1211 |
-
b.tags.domains.forEach((d) => tagDomains.add(d))
|
| 1212 |
-
b.tags.languages.forEach((l) => tagLanguages.add(l))
|
| 1213 |
-
b.tags.tasks.forEach((t) => tagTasks.add(t))
|
| 1214 |
-
}
|
| 1215 |
-
const tags: HierarchyTags = {
|
| 1216 |
-
domains: Array.from(tagDomains).sort(),
|
| 1217 |
-
languages: Array.from(tagLanguages).sort(),
|
| 1218 |
-
tasks: Array.from(tagTasks).sort(),
|
| 1219 |
-
}
|
| 1220 |
-
|
| 1221 |
-
const family: HierarchyFamily = {
|
| 1222 |
-
key: bucket.key,
|
| 1223 |
-
display_name: display,
|
| 1224 |
-
category: "General",
|
| 1225 |
-
tags,
|
| 1226 |
-
has_card: benches.some((b) => b.has_card),
|
| 1227 |
-
eval_summary_ids: Array.from(
|
| 1228 |
-
new Set(benches.flatMap((b) => b.summary_eval_ids ?? [])),
|
| 1229 |
-
),
|
| 1230 |
-
composites: [],
|
| 1231 |
-
standalone_benchmarks: [],
|
| 1232 |
-
}
|
| 1233 |
-
|
| 1234 |
-
if (bucket.kind === "singleton") {
|
| 1235 |
-
// Singleton family — flatten the (potentially N) per-composite
|
| 1236 |
-
// copies of this benchmark into one standalone row by merging
|
| 1237 |
-
// their slice/metric arrays.
|
| 1238 |
-
const merged: HierarchyBenchmark = mergeSingletonBenchmarks(benches)
|
| 1239 |
-
family.standalone_benchmarks!.push(merged)
|
| 1240 |
-
standalone_benchmark_count += 1
|
| 1241 |
-
single_benchmark_count += 1
|
| 1242 |
-
} else {
|
| 1243 |
-
// Multi-benchmark grouping (curated family or composite-implicit).
|
| 1244 |
-
// Emit one legacy composite under the family. De-dup benchmarks
|
| 1245 |
-
// that appear in multiple upstream composites.
|
| 1246 |
-
const seen = new Set<string>()
|
| 1247 |
-
const dedupedBenches: LegacyBenchmark[] = []
|
| 1248 |
-
for (const b of benches) {
|
| 1249 |
-
if (seen.has(b.key)) continue
|
| 1250 |
-
seen.add(b.key)
|
| 1251 |
-
dedupedBenches.push(b)
|
| 1252 |
-
}
|
| 1253 |
-
family.composites!.push({
|
| 1254 |
-
key: bucket.key,
|
| 1255 |
-
display_name: display,
|
| 1256 |
-
has_card: family.has_card,
|
| 1257 |
-
category: family.category,
|
| 1258 |
-
tags,
|
| 1259 |
-
benchmarks: dedupedBenches,
|
| 1260 |
-
})
|
| 1261 |
-
synthesised_composite_count += 1
|
| 1262 |
-
}
|
| 1263 |
-
|
| 1264 |
-
void distinctBenchmarkKeys
|
| 1265 |
-
families.push(family)
|
| 1266 |
-
}
|
| 1267 |
-
|
| 1268 |
-
const stats = raw.stats
|
| 1269 |
-
? {
|
| 1270 |
-
...raw.stats,
|
| 1271 |
-
standalone_benchmark_count:
|
| 1272 |
-
raw.stats.standalone_benchmark_count ?? standalone_benchmark_count,
|
| 1273 |
-
single_benchmark_count:
|
| 1274 |
-
raw.stats.single_benchmark_count ?? single_benchmark_count,
|
| 1275 |
-
}
|
| 1276 |
-
: {
|
| 1277 |
-
family_count: families.length,
|
| 1278 |
-
composite_count: raw.composites?.length ?? synthesised_composite_count,
|
| 1279 |
-
benchmark_count: 0,
|
| 1280 |
-
standalone_benchmark_count,
|
| 1281 |
-
single_benchmark_count,
|
| 1282 |
-
slice_count: 0,
|
| 1283 |
-
metric_count: 0,
|
| 1284 |
-
metric_rows_scanned: 0,
|
| 1285 |
-
}
|
| 1286 |
-
|
| 1287 |
-
return {
|
| 1288 |
-
...raw,
|
| 1289 |
-
families,
|
| 1290 |
-
stats,
|
| 1291 |
-
}
|
| 1292 |
-
}
|
| 1293 |
-
|
| 1294 |
-
function mergeSingletonBenchmarks(benches: HierarchyBenchmark[]): HierarchyBenchmark {
|
| 1295 |
-
if (benches.length === 1) return benches[0]
|
| 1296 |
-
const first = benches[0]
|
| 1297 |
-
const sliceMap = new Map<string, HierarchySlice>()
|
| 1298 |
-
const metricMap = new Map<string, HierarchyMetric>()
|
| 1299 |
-
const summaryIds = new Set<string>()
|
| 1300 |
-
for (const b of benches) {
|
| 1301 |
-
for (const s of b.slices ?? []) {
|
| 1302 |
-
if (!sliceMap.has(s.key)) sliceMap.set(s.key, s)
|
| 1303 |
-
}
|
| 1304 |
-
for (const m of b.metrics ?? []) {
|
| 1305 |
-
if (!metricMap.has(m.key)) metricMap.set(m.key, m)
|
| 1306 |
-
}
|
| 1307 |
-
for (const id of b.summary_eval_ids ?? []) summaryIds.add(id)
|
| 1308 |
}
|
| 1309 |
-
|
| 1310 |
-
.
|
| 1311 |
-
|
| 1312 |
-
|
| 1313 |
-
|
| 1314 |
}
|
|
|
|
| 1315 |
}
|
| 1316 |
|
| 1317 |
export async function fetchComparisonIndex(): Promise<ComparisonIndex> {
|
|
@@ -1805,11 +1454,10 @@ function flattenHierarchyNode(
|
|
| 1805 |
? `${context.benchmark_parent_name ?? context.benchmark} / ${metric.slice_name}`
|
| 1806 |
: context.canonical_display_name ?? context.benchmark),
|
| 1807 |
category,
|
| 1808 |
-
|
| 1809 |
benchmark_family_name: context.benchmark_family_name,
|
| 1810 |
-
|
| 1811 |
benchmark_parent_name: context.benchmark_parent_name,
|
| 1812 |
-
benchmark_leaf_key: metric.benchmark_leaf_key ?? context.benchmark_leaf_key,
|
| 1813 |
benchmark_leaf_name: metric.benchmark_leaf_name ?? context.benchmark_leaf_name,
|
| 1814 |
slice_key: sliceKey,
|
| 1815 |
slice_name: sliceName,
|
|
|
|
| 932 |
* back under per-family records, with the composite slug as the
|
| 933 |
* family key when no curated multi-benchmark family applies.
|
| 934 |
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
/**
|
| 936 |
+
* Validate-and-passthrough for the v3 hierarchy shape (per
|
| 937 |
+
* /Users/jchim/projects/evaleval/notes/hierarchy-alignment.md §5.1).
|
| 938 |
+
* The producer's `write_hierarchy()` emits family-rooted trees
|
| 939 |
+
* directly; the adapter no longer synthesises legacy shapes.
|
| 940 |
*
|
| 941 |
+
* Behaviour:
|
| 942 |
+
* - v3 detection via `schema_version === "v3.hierarchy.1"` —
|
| 943 |
+
* pass through unchanged.
|
| 944 |
+
* - Older snapshot lacking schema_version: log a warning and
|
| 945 |
+
* pass through. Consumers may render empty for missing fields
|
| 946 |
+
* but won't crash.
|
|
|
|
| 947 |
*
|
| 948 |
+
* Step 4 deleted the legacy `adaptCompositeShape()` synthesis
|
| 949 |
+
* (~400 lines of bucketing logic that built families[].composites[]
|
| 950 |
+
* from the old top-level composites[]). The producer now does that
|
| 951 |
+
* grouping at write time using `canonical_composites.family_id`.
|
| 952 |
*/
|
| 953 |
+
export function adaptEvalHierarchy(raw: EvalHierarchy): EvalHierarchy {
|
| 954 |
+
if (!raw) {
|
| 955 |
+
return { families: [] }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 956 |
}
|
| 957 |
+
if (raw.schema_version && !raw.schema_version.startsWith("v3.hierarchy.")) {
|
| 958 |
+
console.warn(
|
| 959 |
+
`adaptEvalHierarchy: unexpected schema_version=${JSON.stringify(raw.schema_version)}; ` +
|
| 960 |
+
`expected v3.hierarchy.*. Frontend may render incompletely.`,
|
| 961 |
+
)
|
| 962 |
}
|
| 963 |
+
return raw
|
| 964 |
}
|
| 965 |
|
| 966 |
export async function fetchComparisonIndex(): Promise<ComparisonIndex> {
|
|
|
|
| 1454 |
? `${context.benchmark_parent_name ?? context.benchmark} / ${metric.slice_name}`
|
| 1455 |
: context.canonical_display_name ?? context.benchmark),
|
| 1456 |
category,
|
| 1457 |
+
family_id: context.benchmark_family_key,
|
| 1458 |
benchmark_family_name: context.benchmark_family_name,
|
| 1459 |
+
parent_benchmark_id: context.benchmark_parent_key,
|
| 1460 |
benchmark_parent_name: context.benchmark_parent_name,
|
|
|
|
| 1461 |
benchmark_leaf_name: metric.benchmark_leaf_name ?? context.benchmark_leaf_name,
|
| 1462 |
slice_key: sliceKey,
|
| 1463 |
slice_name: sliceName,
|
lib/model-data.ts
CHANGED
|
@@ -493,8 +493,8 @@ export function hfEvalEntryToListItem(entry: HFEvalListEntry): BenchmarkEvalList
|
|
| 493 |
metrics_count: entry.metrics_count,
|
| 494 |
metric_names: entry.metric_names,
|
| 495 |
instance_data: entry.instance_data,
|
| 496 |
-
|
| 497 |
-
|
| 498 |
source_data: entry.source_data,
|
| 499 |
top_score: entry.top_score,
|
| 500 |
subtasks_count: entry.subtasks_count ?? 0,
|
|
|
|
| 493 |
metrics_count: entry.metrics_count,
|
| 494 |
metric_names: entry.metric_names,
|
| 495 |
instance_data: entry.instance_data,
|
| 496 |
+
family_id: entry.benchmark_family_key,
|
| 497 |
+
parent_benchmark_id: entry.benchmark_parent_key,
|
| 498 |
source_data: entry.source_data,
|
| 499 |
top_score: entry.top_score,
|
| 500 |
subtasks_count: entry.subtasks_count ?? 0,
|
lib/sidecars.ts
CHANGED
|
@@ -57,7 +57,28 @@ export function fetchHierarchy(): Promise<EvalHierarchy> {
|
|
| 57 |
}
|
| 58 |
|
| 59 |
export function fetchComparisonIndex(): Promise<ComparisonIndex> {
|
| 60 |
-
return (cache.comparisonIndex ??= fetchJson<ComparisonIndex>("comparison-index.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
export function resetSidecarCacheForTests() {
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
export function fetchComparisonIndex(): Promise<ComparisonIndex> {
|
| 60 |
+
return (cache.comparisonIndex ??= fetchJson<ComparisonIndex>("comparison-index.json").then(
|
| 61 |
+
(index) => {
|
| 62 |
+
assertComparisonIndexShape(index)
|
| 63 |
+
return index
|
| 64 |
+
},
|
| 65 |
+
))
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* Fail fast on contract regressions. Comparison-index rows must carry
|
| 70 |
+
* `family_id`; the model-page graph view collapses without it. See
|
| 71 |
+
* `notes/hierarchy-alignment.md` §5.2.
|
| 72 |
+
*/
|
| 73 |
+
export function assertComparisonIndexShape(index: ComparisonIndex): void {
|
| 74 |
+
for (const [evalId, entry] of Object.entries(index.evals ?? {})) {
|
| 75 |
+
if (!Object.prototype.hasOwnProperty.call(entry, "family_id")) {
|
| 76 |
+
throw new Error(
|
| 77 |
+
`comparison-index contract regression: evals[${evalId}] is missing family_id. ` +
|
| 78 |
+
`See notes/hierarchy-alignment.md §5.2.`,
|
| 79 |
+
)
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
}
|
| 83 |
|
| 84 |
export function resetSidecarCacheForTests() {
|
lib/view-data.ts
CHANGED
|
@@ -44,28 +44,23 @@ const MODEL_CARD_COLUMNS = `
|
|
| 44 |
|
| 45 |
// The composite/family/slice taxonomy refactor (eval_card_backend
|
| 46 |
// notes/09-) replaced the legacy `composite_benchmark_key` /
|
| 47 |
-
// `composite_benchmark_name`
|
| 48 |
-
// `
|
| 49 |
-
// `
|
| 50 |
-
//
|
| 51 |
-
//
|
| 52 |
// composite_benchmark_key/name → composite_slug/display_name
|
| 53 |
// (the leaderboard, e.g. "wasp"/"WASP" — what the eval-detail
|
| 54 |
// "Composite" label shows)
|
| 55 |
-
// benchmark_family_key/name → family_id/family_display_name
|
| 56 |
-
// (curated multi-benchmark family, e.g. "judgebench"/"JudgeBench
|
| 57 |
-
// family" — drives the family-table grouping in the legacy
|
| 58 |
-
// hierarchy adapter)
|
| 59 |
const EVAL_LIST_COLUMNS = `
|
| 60 |
evaluation_id, evaluation_name, canonical_display_name,
|
| 61 |
benchmark_id,
|
| 62 |
composite_slug, composite_display_name,
|
| 63 |
family_id, family_display_name, is_slice,
|
|
|
|
| 64 |
composite_slug AS composite_benchmark_key,
|
| 65 |
composite_display_name AS composite_benchmark_name,
|
| 66 |
-
family_id AS benchmark_family_key,
|
| 67 |
family_display_name AS benchmark_family_name,
|
| 68 |
-
CASE WHEN is_slice THEN benchmark_id ELSE NULL END AS benchmark_leaf_key,
|
| 69 |
category,
|
| 70 |
metric_config, models_count, evaluator_names, source_types,
|
| 71 |
latest_source_name, third_party_ratio,
|
|
@@ -89,11 +84,10 @@ const CELL_JOIN_COLUMNS = `
|
|
| 89 |
e.family_id AS eval_family_id,
|
| 90 |
e.family_display_name AS eval_family_display_name,
|
| 91 |
e.is_slice AS eval_is_slice,
|
|
|
|
| 92 |
e.composite_slug AS eval_composite_benchmark_key,
|
| 93 |
e.composite_display_name AS eval_composite_benchmark_name,
|
| 94 |
-
e.family_id AS eval_benchmark_family_key,
|
| 95 |
e.family_display_name AS eval_benchmark_family_name,
|
| 96 |
-
CASE WHEN e.is_slice THEN e.benchmark_id ELSE NULL END AS eval_benchmark_leaf_key,
|
| 97 |
e.category AS eval_category,
|
| 98 |
e.metric_config AS eval_metric_config,
|
| 99 |
e.source_data AS eval_source_data,
|
|
@@ -350,12 +344,12 @@ function reshapeCellToBenchmarkEvaluation(row: Row): BenchmarkEvaluation {
|
|
| 350 |
display_name: optionalString(row.eval_evaluation_name),
|
| 351 |
canonical_display_name: optionalString(row.eval_canonical_display_name),
|
| 352 |
category: normalizeCategory(row.eval_category ?? row.category),
|
| 353 |
-
|
| 354 |
-
benchmark_family_name: optionalString(row.
|
| 355 |
-
|
| 356 |
benchmark_parent_name: optionalString(row.eval_composite_benchmark_name),
|
| 357 |
-
benchmark_leaf_key: optionalString(row.eval_benchmark_leaf_key),
|
| 358 |
benchmark_leaf_name: optionalString(row.eval_evaluation_name),
|
|
|
|
| 359 |
is_summary_score: Boolean(row.eval_is_summary_score ?? row.is_summary_score),
|
| 360 |
source_data: sourceDataFromRow(row),
|
| 361 |
source_metadata: sourceMetadataFromRow(row),
|
|
|
|
| 44 |
|
| 45 |
// The composite/family/slice taxonomy refactor (eval_card_backend
|
| 46 |
// notes/09-) replaced the legacy `composite_benchmark_key` /
|
| 47 |
+
// `composite_benchmark_name` columns with `composite_slug` /
|
| 48 |
+
// `composite_display_name`. The `family_id` / `family_display_name` /
|
| 49 |
+
// `is_slice` columns are the canonical identity surface; we still
|
| 50 |
+
// alias the composite_* legacy names for backward compat with
|
| 51 |
+
// consumers that haven't migrated yet. Mapping:
|
| 52 |
// composite_benchmark_key/name → composite_slug/display_name
|
| 53 |
// (the leaderboard, e.g. "wasp"/"WASP" — what the eval-detail
|
| 54 |
// "Composite" label shows)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
const EVAL_LIST_COLUMNS = `
|
| 56 |
evaluation_id, evaluation_name, canonical_display_name,
|
| 57 |
benchmark_id,
|
| 58 |
composite_slug, composite_display_name,
|
| 59 |
family_id, family_display_name, is_slice,
|
| 60 |
+
parent_benchmark_id,
|
| 61 |
composite_slug AS composite_benchmark_key,
|
| 62 |
composite_display_name AS composite_benchmark_name,
|
|
|
|
| 63 |
family_display_name AS benchmark_family_name,
|
|
|
|
| 64 |
category,
|
| 65 |
metric_config, models_count, evaluator_names, source_types,
|
| 66 |
latest_source_name, third_party_ratio,
|
|
|
|
| 84 |
e.family_id AS eval_family_id,
|
| 85 |
e.family_display_name AS eval_family_display_name,
|
| 86 |
e.is_slice AS eval_is_slice,
|
| 87 |
+
e.parent_benchmark_id AS eval_parent_benchmark_id,
|
| 88 |
e.composite_slug AS eval_composite_benchmark_key,
|
| 89 |
e.composite_display_name AS eval_composite_benchmark_name,
|
|
|
|
| 90 |
e.family_display_name AS eval_benchmark_family_name,
|
|
|
|
| 91 |
e.category AS eval_category,
|
| 92 |
e.metric_config AS eval_metric_config,
|
| 93 |
e.source_data AS eval_source_data,
|
|
|
|
| 344 |
display_name: optionalString(row.eval_evaluation_name),
|
| 345 |
canonical_display_name: optionalString(row.eval_canonical_display_name),
|
| 346 |
category: normalizeCategory(row.eval_category ?? row.category),
|
| 347 |
+
family_id: optionalString(row.eval_family_id),
|
| 348 |
+
benchmark_family_name: optionalString(row.eval_family_display_name),
|
| 349 |
+
parent_benchmark_id: optionalString(row.eval_parent_benchmark_id),
|
| 350 |
benchmark_parent_name: optionalString(row.eval_composite_benchmark_name),
|
|
|
|
| 351 |
benchmark_leaf_name: optionalString(row.eval_evaluation_name),
|
| 352 |
+
is_slice: Boolean(row.eval_is_slice),
|
| 353 |
is_summary_score: Boolean(row.eval_is_summary_score ?? row.is_summary_score),
|
| 354 |
source_data: sourceDataFromRow(row),
|
| 355 |
source_metadata: sourceMetadataFromRow(row),
|
tests/__snapshots__/adapters.test.ts.snap
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tests/adapters.test.ts
CHANGED
|
@@ -55,7 +55,7 @@ describe("flattenModelEvaluations", () => {
|
|
| 55 |
const evaluations = flattenModelEvaluations(input)
|
| 56 |
// Snapshot a digest rather than the full output (which can be 10k+ lines
|
| 57 |
// for large models). The digest captures: count, distinct categories,
|
| 58 |
-
// distinct evaluator_relationships, count of distinct
|
| 59 |
// count of unique evaluation_ids, and a hash of the full output. Any change
|
| 60 |
// to the full output changes the hash; the structured fields make the diff
|
| 61 |
// readable when something changes.
|
|
@@ -92,7 +92,7 @@ function digestEvaluations(evaluations: BenchmarkEvaluation[]) {
|
|
| 92 |
let missingSourceMetadata = 0
|
| 93 |
for (const e of evaluations) {
|
| 94 |
if (e.category) categories.add(e.category)
|
| 95 |
-
if (e.
|
| 96 |
if (e.source_metadata?.evaluator_relationship) evaluators.add(e.source_metadata.evaluator_relationship)
|
| 97 |
if (e.evaluation_id) evaluationIds.add(e.evaluation_id)
|
| 98 |
if (!e.source_metadata) missingSourceMetadata += 1
|
|
@@ -101,7 +101,7 @@ function digestEvaluations(evaluations: BenchmarkEvaluation[]) {
|
|
| 101 |
count: evaluations.length,
|
| 102 |
distinct_evaluation_ids: evaluationIds.size,
|
| 103 |
distinct_categories: [...categories].sort(),
|
| 104 |
-
|
| 105 |
distinct_evaluator_relationships: [...evaluators].sort(),
|
| 106 |
missing_source_metadata: missingSourceMetadata,
|
| 107 |
full_output_hash: stableHash(evaluations),
|
|
|
|
| 55 |
const evaluations = flattenModelEvaluations(input)
|
| 56 |
// Snapshot a digest rather than the full output (which can be 10k+ lines
|
| 57 |
// for large models). The digest captures: count, distinct categories,
|
| 58 |
+
// distinct evaluator_relationships, count of distinct family_ids,
|
| 59 |
// count of unique evaluation_ids, and a hash of the full output. Any change
|
| 60 |
// to the full output changes the hash; the structured fields make the diff
|
| 61 |
// readable when something changes.
|
|
|
|
| 92 |
let missingSourceMetadata = 0
|
| 93 |
for (const e of evaluations) {
|
| 94 |
if (e.category) categories.add(e.category)
|
| 95 |
+
if (e.family_id) families.add(e.family_id)
|
| 96 |
if (e.source_metadata?.evaluator_relationship) evaluators.add(e.source_metadata.evaluator_relationship)
|
| 97 |
if (e.evaluation_id) evaluationIds.add(e.evaluation_id)
|
| 98 |
if (!e.source_metadata) missingSourceMetadata += 1
|
|
|
|
| 101 |
count: evaluations.length,
|
| 102 |
distinct_evaluation_ids: evaluationIds.size,
|
| 103 |
distinct_categories: [...categories].sort(),
|
| 104 |
+
distinct_family_ids: families.size,
|
| 105 |
distinct_evaluator_relationships: [...evaluators].sort(),
|
| 106 |
missing_source_metadata: missingSourceMetadata,
|
| 107 |
full_output_hash: stableHash(evaluations),
|
tests/comparison-index-loader.test.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { mkdir, mkdtemp, rm, writeFile } from "fs/promises"
|
| 2 |
+
import os from "os"
|
| 3 |
+
import path from "path"
|
| 4 |
+
|
| 5 |
+
import { describe, expect, it } from "vitest"
|
| 6 |
+
|
| 7 |
+
import type { ComparisonIndex } from "../lib/backend-artifacts"
|
| 8 |
+
import { assertComparisonIndexShape } from "../lib/sidecars"
|
| 9 |
+
|
| 10 |
+
// Shared fixture: a comparison-index that mirrors the post-migration
|
| 11 |
+
// shape from notes/hierarchy-alignment.md §5.2. Tests mutate copies of
|
| 12 |
+
// this to exercise contract assertions.
|
| 13 |
+
const validFixture: ComparisonIndex = {
|
| 14 |
+
generated_at: "2026-05-04T00:00:00Z",
|
| 15 |
+
config_version: 2,
|
| 16 |
+
metric_group_order: ["capability", "robustness", "efficiency", "cost", "latency", "rank", "other"],
|
| 17 |
+
evals: {
|
| 18 |
+
"helm-classic%2Fmmlu": {
|
| 19 |
+
eval_summary_id: "helm-classic%2Fmmlu",
|
| 20 |
+
benchmark_id: "mmlu",
|
| 21 |
+
family_id: "helm",
|
| 22 |
+
family_display_name: "HELM",
|
| 23 |
+
composite_slug: "helm-classic",
|
| 24 |
+
composite_display_name: "HELM Classic",
|
| 25 |
+
parent_benchmark_id: null,
|
| 26 |
+
display_name: "MMLU",
|
| 27 |
+
category: "Knowledge",
|
| 28 |
+
is_slice: false,
|
| 29 |
+
is_summary_score: false,
|
| 30 |
+
summary_score_for: null,
|
| 31 |
+
summary_eval_ids: [],
|
| 32 |
+
metrics: [],
|
| 33 |
+
},
|
| 34 |
+
"helm-classic%2Fmmlu%2Fanatomy": {
|
| 35 |
+
eval_summary_id: "helm-classic%2Fmmlu%2Fanatomy",
|
| 36 |
+
benchmark_id: "mmlu",
|
| 37 |
+
family_id: "helm",
|
| 38 |
+
family_display_name: "HELM",
|
| 39 |
+
composite_slug: "helm-classic",
|
| 40 |
+
composite_display_name: "HELM Classic",
|
| 41 |
+
parent_benchmark_id: "mmlu",
|
| 42 |
+
display_name: "MMLU Anatomy",
|
| 43 |
+
category: "Knowledge",
|
| 44 |
+
is_slice: true,
|
| 45 |
+
is_summary_score: false,
|
| 46 |
+
summary_score_for: null,
|
| 47 |
+
summary_eval_ids: [],
|
| 48 |
+
metrics: [],
|
| 49 |
+
},
|
| 50 |
+
},
|
| 51 |
+
by_model: {},
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
describe("assertComparisonIndexShape", () => {
|
| 55 |
+
it("accepts entries with family_id present", () => {
|
| 56 |
+
expect(() => assertComparisonIndexShape(validFixture)).not.toThrow()
|
| 57 |
+
})
|
| 58 |
+
|
| 59 |
+
it("accepts the new parent_benchmark_id field on slice entries", () => {
|
| 60 |
+
const sliceEntry = validFixture.evals["helm-classic%2Fmmlu%2Fanatomy"]
|
| 61 |
+
expect(sliceEntry.parent_benchmark_id).toBe("mmlu")
|
| 62 |
+
expect(sliceEntry.is_slice).toBe(true)
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
it("rejects entries that are missing family_id", () => {
|
| 66 |
+
const broken = JSON.parse(JSON.stringify(validFixture)) as ComparisonIndex
|
| 67 |
+
delete (broken.evals["helm-classic%2Fmmlu"] as unknown as Record<string, unknown>).family_id
|
| 68 |
+
|
| 69 |
+
expect(() => assertComparisonIndexShape(broken)).toThrow(
|
| 70 |
+
/missing family_id/,
|
| 71 |
+
)
|
| 72 |
+
})
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
describe("fetchComparisonIndex (v2 sidecar)", () => {
|
| 76 |
+
it("validates the loaded sidecar against the contract", async () => {
|
| 77 |
+
const snapshotDir = await mkdtemp(path.join(os.tmpdir(), "eval-card-cmp-"))
|
| 78 |
+
const previousBackend = process.env.DATA_BACKEND
|
| 79 |
+
const previousSnapshotUrl = process.env.SNAPSHOT_URL
|
| 80 |
+
|
| 81 |
+
try {
|
| 82 |
+
await mkdir(snapshotDir, { recursive: true })
|
| 83 |
+
const broken = JSON.parse(JSON.stringify(validFixture)) as ComparisonIndex
|
| 84 |
+
delete (broken.evals["helm-classic%2Fmmlu"] as unknown as Record<string, unknown>).family_id
|
| 85 |
+
await writeFile(
|
| 86 |
+
path.join(snapshotDir, "comparison-index.json"),
|
| 87 |
+
JSON.stringify(broken),
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
process.env.DATA_BACKEND = "v2"
|
| 91 |
+
process.env.SNAPSHOT_URL = `file://${snapshotDir}`
|
| 92 |
+
|
| 93 |
+
const sidecars = await import("../lib/sidecars")
|
| 94 |
+
sidecars.resetSidecarCacheForTests()
|
| 95 |
+
|
| 96 |
+
await expect(sidecars.fetchComparisonIndex()).rejects.toThrow(
|
| 97 |
+
/missing family_id/,
|
| 98 |
+
)
|
| 99 |
+
} finally {
|
| 100 |
+
const sidecars = await import("../lib/sidecars")
|
| 101 |
+
sidecars.resetSidecarCacheForTests()
|
| 102 |
+
if (previousBackend == null) {
|
| 103 |
+
delete process.env.DATA_BACKEND
|
| 104 |
+
} else {
|
| 105 |
+
process.env.DATA_BACKEND = previousBackend
|
| 106 |
+
}
|
| 107 |
+
if (previousSnapshotUrl == null) {
|
| 108 |
+
delete process.env.SNAPSHOT_URL
|
| 109 |
+
} else {
|
| 110 |
+
process.env.SNAPSHOT_URL = previousSnapshotUrl
|
| 111 |
+
}
|
| 112 |
+
await rm(snapshotDir, { recursive: true, force: true })
|
| 113 |
+
}
|
| 114 |
+
})
|
| 115 |
+
})
|
tests/eval-hierarchy-adapter.test.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
| 1 |
-
import { describe, expect, it } from "vitest"
|
| 2 |
|
| 3 |
import type { EvalHierarchy } from "../lib/backend-artifacts"
|
| 4 |
import { adaptEvalHierarchy } from "../lib/hf-data"
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
stats: {
|
| 10 |
family_count: 1,
|
| 11 |
composite_count: 1,
|
|
@@ -18,22 +26,19 @@ describe("adaptEvalHierarchy", () => {
|
|
| 18 |
{
|
| 19 |
key: "gpqa",
|
| 20 |
display_name: "GPQA family",
|
| 21 |
-
|
| 22 |
-
},
|
| 23 |
-
],
|
| 24 |
-
composites: [
|
| 25 |
-
{
|
| 26 |
-
key: "wasp",
|
| 27 |
-
display_name: "WASP",
|
| 28 |
-
category: "Reasoning",
|
| 29 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
|
|
|
|
|
|
| 30 |
benchmarks: [
|
| 31 |
{
|
| 32 |
key: "gpqa",
|
| 33 |
display_name: "GPQA",
|
| 34 |
-
has_card: false,
|
| 35 |
family_id: "gpqa",
|
| 36 |
is_slice: false,
|
|
|
|
|
|
|
|
|
|
| 37 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
| 38 |
metrics: [{ key: "accuracy", display_name: "Accuracy" }],
|
| 39 |
slices: [],
|
|
@@ -42,9 +47,11 @@ describe("adaptEvalHierarchy", () => {
|
|
| 42 |
{
|
| 43 |
key: "gpqa-diamond",
|
| 44 |
display_name: "GPQA Diamond",
|
| 45 |
-
has_card: false,
|
| 46 |
family_id: "gpqa",
|
| 47 |
is_slice: false,
|
|
|
|
|
|
|
|
|
|
| 48 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
| 49 |
metrics: [{ key: "accuracy", display_name: "Accuracy" }],
|
| 50 |
slices: [],
|
|
@@ -53,22 +60,44 @@ describe("adaptEvalHierarchy", () => {
|
|
| 53 |
],
|
| 54 |
},
|
| 55 |
],
|
| 56 |
-
}
|
| 57 |
|
| 58 |
const adapted = adaptEvalHierarchy(raw)
|
| 59 |
-
const gpqa = adapted.families.find((family) => family.key === "gpqa")
|
| 60 |
|
| 61 |
-
expect(
|
| 62 |
-
expect(
|
| 63 |
-
expect(
|
| 64 |
-
expect(
|
| 65 |
"gpqa",
|
| 66 |
"gpqa-diamond",
|
| 67 |
])
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
})
|
| 74 |
})
|
|
|
|
| 1 |
+
import { describe, expect, it, vi } from "vitest"
|
| 2 |
|
| 3 |
import type { EvalHierarchy } from "../lib/backend-artifacts"
|
| 4 |
import { adaptEvalHierarchy } from "../lib/hf-data"
|
| 5 |
|
| 6 |
+
// adaptEvalHierarchy is a passthrough validator post-Step-4 — the
|
| 7 |
+
// producer's write_hierarchy() emits the v3 family-rooted tree
|
| 8 |
+
// directly (notes/hierarchy-alignment.md §5.1), so the adapter no
|
| 9 |
+
// longer synthesises legacy shapes. These tests confirm the
|
| 10 |
+
// passthrough preserves data and that the schema_version warning
|
| 11 |
+
// fires for unknown versions.
|
| 12 |
+
|
| 13 |
+
describe("adaptEvalHierarchy (passthrough)", () => {
|
| 14 |
+
it("returns the v3 hierarchy unchanged", () => {
|
| 15 |
+
const raw: EvalHierarchy = {
|
| 16 |
+
schema_version: "v3.hierarchy.1",
|
| 17 |
stats: {
|
| 18 |
family_count: 1,
|
| 19 |
composite_count: 1,
|
|
|
|
| 26 |
{
|
| 27 |
key: "gpqa",
|
| 28 |
display_name: "GPQA family",
|
| 29 |
+
category: "knowledge",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
| 31 |
+
evals_count: 4,
|
| 32 |
+
eval_summary_ids: ["wasp%2Fgpqa", "wasp%2Fgpqa-diamond"],
|
| 33 |
benchmarks: [
|
| 34 |
{
|
| 35 |
key: "gpqa",
|
| 36 |
display_name: "GPQA",
|
|
|
|
| 37 |
family_id: "gpqa",
|
| 38 |
is_slice: false,
|
| 39 |
+
is_overall: true,
|
| 40 |
+
is_primary: true,
|
| 41 |
+
has_card: false,
|
| 42 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
| 43 |
metrics: [{ key: "accuracy", display_name: "Accuracy" }],
|
| 44 |
slices: [],
|
|
|
|
| 47 |
{
|
| 48 |
key: "gpqa-diamond",
|
| 49 |
display_name: "GPQA Diamond",
|
|
|
|
| 50 |
family_id: "gpqa",
|
| 51 |
is_slice: false,
|
| 52 |
+
is_overall: false,
|
| 53 |
+
is_primary: false,
|
| 54 |
+
has_card: false,
|
| 55 |
tags: { domains: ["reasoning"], languages: [], tasks: ["qa"] },
|
| 56 |
metrics: [{ key: "accuracy", display_name: "Accuracy" }],
|
| 57 |
slices: [],
|
|
|
|
| 60 |
],
|
| 61 |
},
|
| 62 |
],
|
| 63 |
+
}
|
| 64 |
|
| 65 |
const adapted = adaptEvalHierarchy(raw)
|
|
|
|
| 66 |
|
| 67 |
+
expect(adapted).toBe(raw) // passthrough: same reference
|
| 68 |
+
expect(adapted.families).toHaveLength(1)
|
| 69 |
+
expect(adapted.families[0].benchmarks).toHaveLength(2)
|
| 70 |
+
expect(adapted.families[0].benchmarks?.map((b) => b.key)).toEqual([
|
| 71 |
"gpqa",
|
| 72 |
"gpqa-diamond",
|
| 73 |
])
|
| 74 |
+
})
|
| 75 |
+
|
| 76 |
+
it("returns a safe empty shape on null/undefined input", () => {
|
| 77 |
+
expect(adaptEvalHierarchy(null as unknown as EvalHierarchy)).toEqual({
|
| 78 |
+
families: [],
|
| 79 |
+
})
|
| 80 |
+
})
|
| 81 |
+
|
| 82 |
+
it("warns on unknown schema_version but still passes through", () => {
|
| 83 |
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
| 84 |
+
const raw: EvalHierarchy = {
|
| 85 |
+
schema_version: "v2.hierarchy.999",
|
| 86 |
+
families: [],
|
| 87 |
+
}
|
| 88 |
+
const adapted = adaptEvalHierarchy(raw)
|
| 89 |
+
expect(adapted).toBe(raw)
|
| 90 |
+
expect(warn).toHaveBeenCalledOnce()
|
| 91 |
+
warn.mockRestore()
|
| 92 |
+
})
|
| 93 |
+
|
| 94 |
+
it("does not warn when schema_version matches v3.hierarchy.*", () => {
|
| 95 |
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
| 96 |
+
adaptEvalHierarchy({
|
| 97 |
+
schema_version: "v3.hierarchy.1",
|
| 98 |
+
families: [],
|
| 99 |
+
})
|
| 100 |
+
expect(warn).not.toHaveBeenCalled()
|
| 101 |
+
warn.mockRestore()
|
| 102 |
})
|
| 103 |
})
|
tests/view-data.test.ts
CHANGED
|
@@ -109,8 +109,12 @@ async function writeSyntheticStageJSnapshot(snapshotDir: string) {
|
|
| 109 |
'MMLU' AS canonical_display_name,
|
| 110 |
'mmlu' AS composite_benchmark_key,
|
| 111 |
'MMLU' AS composite_benchmark_name,
|
| 112 |
-
'mmlu' AS
|
| 113 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
'Reasoning' AS category,
|
| 115 |
struct_pack(
|
| 116 |
evaluation_description := 'Accuracy on MMLU',
|
|
|
|
| 109 |
'MMLU' AS canonical_display_name,
|
| 110 |
'mmlu' AS composite_benchmark_key,
|
| 111 |
'MMLU' AS composite_benchmark_name,
|
| 112 |
+
'mmlu' AS composite_slug,
|
| 113 |
+
'MMLU' AS composite_display_name,
|
| 114 |
+
'mmlu' AS family_id,
|
| 115 |
+
'MMLU' AS family_display_name,
|
| 116 |
+
false AS is_slice,
|
| 117 |
+
NULL AS parent_benchmark_id,
|
| 118 |
'Reasoning' AS category,
|
| 119 |
struct_pack(
|
| 120 |
evaluation_description := 'Accuracy on MMLU',
|