j-chim commited on
Commit
4945098
·
1 Parent(s): 66f4cb7

Fix model comparison + add evaluator logo

Browse files
.gitattributes CHANGED
@@ -35,3 +35,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  public/tutorials/screenshots/*.png filter=lfs diff=lfs merge=lfs -text
37
  public/tutorials/screenshots/*.gif filter=lfs diff=lfs merge=lfs -text
 
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  public/tutorials/screenshots/*.png filter=lfs diff=lfs merge=lfs -text
37
  public/tutorials/screenshots/*.gif filter=lfs diff=lfs merge=lfs -text
38
+ public/org-logos/*.png filter=lfs diff=lfs merge=lfs -text
39
+ public/org-logos/*.webp filter=lfs diff=lfs merge=lfs -text
app/api/org-metadata/route.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from "next/server"
2
+
3
+ import { getOrganizationsData } from "@/lib/data-backend"
4
+
5
+ export async function GET() {
6
+ const orgs = await getOrganizationsData()
7
+ return NextResponse.json(orgs)
8
+ }
app/evals/page.tsx CHANGED
@@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from "next/navigation"
5
  import { Search } from "lucide-react"
6
 
7
  import { EvaluatorTable, type EvaluatorTableSortCol } from "@/components/evaluator-table"
 
8
  import { FamilyTable, getFamilyNavId, type FamilySortCol } from "@/components/family-table"
9
  import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
10
  import { Navigation } from "@/components/navigation"
@@ -172,9 +173,10 @@ function EvalsPageInner() {
172
 
173
  // Evaluator groups (group-by-Evaluator mode). Verified filter is
174
  // evaluator-aware: counts only (eval, org) pairs where org is verified.
 
175
  const evaluatorGroups = useMemo(
176
- () => groupEvalsByEvaluator(allEvals, { verifiedOnly }),
177
- [allEvals, verifiedOnly],
178
  )
179
 
180
  const filteredEvaluators = useMemo(() => {
 
5
  import { Search } from "lucide-react"
6
 
7
  import { EvaluatorTable, type EvaluatorTableSortCol } from "@/components/evaluator-table"
8
+ import { useOrgMetadata } from "@/components/org-metadata-provider"
9
  import { FamilyTable, getFamilyNavId, type FamilySortCol } from "@/components/family-table"
10
  import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
11
  import { Navigation } from "@/components/navigation"
 
173
 
174
  // Evaluator groups (group-by-Evaluator mode). Verified filter is
175
  // evaluator-aware: counts only (eval, org) pairs where org is verified.
176
+ const orgMeta = useOrgMetadata()
177
  const evaluatorGroups = useMemo(
178
+ () => groupEvalsByEvaluator(allEvals, { verifiedOnly, orgMeta }),
179
+ [allEvals, verifiedOnly, orgMeta],
180
  )
181
 
182
  const filteredEvaluators = useMemo(() => {
app/evaluators/[...id]/page.tsx CHANGED
@@ -4,13 +4,15 @@ import { Suspense, useCallback, useEffect, useMemo, useState } from "react"
4
  import { useParams, useRouter } from "next/navigation"
5
  import { ArrowLeft, Search } from "lucide-react"
6
 
 
 
7
  import { FamilyTable, type FamilySortCol } from "@/components/family-table"
8
  import { Navigation } from "@/components/navigation"
9
- import { VerifiedBadge } from "@/components/signals/verified-badge"
10
  import type { EvalHierarchy } from "@/lib/backend-artifacts"
11
  import type { BenchmarkCard } from "@/lib/benchmark-schema"
12
  import { fetchBenchmarkMetadata, fetchEvalHierarchy, fetchEvalList } from "@/lib/dashboard-data-client"
13
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
 
14
  import { getEvalsForEvaluator, isRecognizedEvaluator } from "@/lib/evaluators"
15
 
16
  function EvaluatorDetailInner() {
@@ -28,6 +30,9 @@ function EvaluatorDetailInner() {
28
  const [allEvals, setAllEvals] = useState<BenchmarkEvalListItem[]>([])
29
  const [hierarchy, setHierarchy] = useState<EvalHierarchy | null>(null)
30
  const [benchmarkCards, setBenchmarkCards] = useState<Record<string, BenchmarkCard>>({})
 
 
 
31
  const [loading, setLoading] = useState(true)
32
  const [error, setError] = useState<string | null>(null)
33
  const [searchQuery, setSearchQuery] = useState("")
@@ -66,8 +71,8 @@ function EvaluatorDetailInner() {
66
  // this route (the header reports total + verified counts separately, so no
67
  // information is hidden). See evaluator-table.tsx hrefFor.
68
  const { name, isVerified, evals } = useMemo(
69
- () => getEvalsForEvaluator(allEvals, slug),
70
- [allEvals, slug],
71
  )
72
 
73
  // Eval-id universe owned by this evaluator — restricts the family tree to
@@ -160,24 +165,17 @@ function EvaluatorDetailInner() {
160
  Evaluators
161
  </button>
162
 
163
- {/* HEADER --------------------------------------------------- */}
164
- <div className="kicker">Evaluator</div>
165
- <h1 className="ec-page-h1 inline-flex items-center gap-2">
166
- {name}
167
- <VerifiedBadge verified={isVerified} recognized={isRecognizedEvaluator(name)} size="md" />
168
- </h1>
169
- <div
170
- className="mb-5 flex flex-wrap items-center gap-3 font-mono text-[11px] uppercase tracking-[0.12em]"
171
- style={{ color: "var(--fg-muted)" }}
172
- >
173
- <span>Reporting organisation</span>
174
- <span style={{ color: "var(--fg-subtle)" }}>·</span>
175
- <span>
176
- {familyCount} {familyCount === 1 ? "family" : "families"}
177
- </span>
178
- <span style={{ color: "var(--fg-subtle)" }}>·</span>
179
- <span>{verifiedCount} verified</span>
180
- </div>
181
  <p className="ec-page-lede">
182
  Reported <strong>{evals.length.toLocaleString()}</strong>{" "}
183
  {evals.length === 1 ? "evaluation" : "evaluations"} across{" "}
 
4
  import { useParams, useRouter } from "next/navigation"
5
  import { ArrowLeft, Search } from "lucide-react"
6
 
7
+ import EvaluatorLetterhead from "@/components/evaluator/evaluator-letterhead"
8
+ import { useOrgMetadata } from "@/components/org-metadata-provider"
9
  import { FamilyTable, type FamilySortCol } from "@/components/family-table"
10
  import { Navigation } from "@/components/navigation"
 
11
  import type { EvalHierarchy } from "@/lib/backend-artifacts"
12
  import type { BenchmarkCard } from "@/lib/benchmark-schema"
13
  import { fetchBenchmarkMetadata, fetchEvalHierarchy, fetchEvalList } from "@/lib/dashboard-data-client"
14
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
15
+ import { normalizeOrgKey } from "@/lib/evaluator-logo"
16
  import { getEvalsForEvaluator, isRecognizedEvaluator } from "@/lib/evaluators"
17
 
18
  function EvaluatorDetailInner() {
 
30
  const [allEvals, setAllEvals] = useState<BenchmarkEvalListItem[]>([])
31
  const [hierarchy, setHierarchy] = useState<EvalHierarchy | null>(null)
32
  const [benchmarkCards, setBenchmarkCards] = useState<Record<string, BenchmarkCard>>({})
33
+ // Org metadata (homepage url, logo, stable id) comes from the app-wide
34
+ // provider (server-fetched in the root layout) — no per-page fetch, no flash.
35
+ const orgMeta = useOrgMetadata()
36
  const [loading, setLoading] = useState(true)
37
  const [error, setError] = useState<string | null>(null)
38
  const [searchQuery, setSearchQuery] = useState("")
 
71
  // this route (the header reports total + verified counts separately, so no
72
  // information is hidden). See evaluator-table.tsx hrefFor.
73
  const { name, isVerified, evals } = useMemo(
74
+ () => getEvalsForEvaluator(allEvals, slug, { orgMeta }),
75
+ [allEvals, slug, orgMeta],
76
  )
77
 
78
  // Eval-id universe owned by this evaluator — restricts the family tree to
 
165
  Evaluators
166
  </button>
167
 
168
+ {/* HEADER — editorial letterhead masthead -------------------- */}
169
+ <EvaluatorLetterhead
170
+ name={name}
171
+ logoSrc={orgMeta[normalizeOrgKey(name)]?.logo ?? null}
172
+ homepageUrl={orgMeta[normalizeOrgKey(name)]?.url}
173
+ isVerified={isVerified}
174
+ recognized={isRecognizedEvaluator(name)}
175
+ evalCount={evals.length}
176
+ familyCount={familyCount}
177
+ verifiedCount={verifiedCount}
178
+ />
 
 
 
 
 
 
 
179
  <p className="ec-page-lede">
180
  Reported <strong>{evals.length.toLocaleString()}</strong>{" "}
181
  {evals.length === 1 ? "evaluation" : "evaluations"} across{" "}
app/globals.css CHANGED
@@ -39,6 +39,13 @@
39
  --nav-bg: rgba(255, 255, 254, 0.95);
40
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.07);
41
 
 
 
 
 
 
 
 
42
  /* Tracking */
43
  --tracking-wide: 0.025em;
44
  --tracking-widest: 0.15em;
 
39
  --nav-bg: rgba(255, 255, 254, 0.95);
40
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.07);
41
 
42
+ /* Logo plate — a subtly warm near-white surface for framing org brand marks.
43
+ Deliberately defined ONLY here (not under .dark) so it never flips to a
44
+ dark surface: near-black or transparent marks (e.g. Anthropic) stay legible
45
+ on the evaluator page in both themes. Warmer than #fff so it sits in the
46
+ warm-neutral system rather than reading as a cold clinical white. */
47
+ --logo-plate: #fdfcfa;
48
+
49
  /* Tracking */
50
  --tracking-wide: 0.025em;
51
  --tracking-widest: 0.15em;
app/layout.tsx CHANGED
@@ -3,9 +3,11 @@ import type { Metadata } from "next"
3
  import "./globals.css"
4
  import { AudienceModeProvider } from "@/components/audience-mode-provider"
5
  import { BackendRefreshListener } from "@/components/backend-refresh-listener"
 
6
  import { QuickStartProvider } from "@/components/quick-start"
7
  import { SiteFooter } from "@/components/site-footer"
8
  import { ThemeProvider } from "@/components/theme-provider"
 
9
 
10
  const SITE_URL = "https://evalcards.evalevalai.com"
11
  const SITE_NAME = "Evaluation Cards"
@@ -59,21 +61,27 @@ export const metadata: Metadata = {
59
  },
60
  }
61
 
62
- export default function RootLayout({
63
  children,
64
  }: Readonly<{
65
  children: React.ReactNode
66
  }>) {
 
 
 
 
67
  return (
68
  <html lang="en" className="antialiased" suppressHydrationWarning>
69
  <body className="font-sans">
70
  <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
71
  <AudienceModeProvider>
72
- <QuickStartProvider>
73
- <BackendRefreshListener />
74
- {children}
75
- <SiteFooter />
76
- </QuickStartProvider>
 
 
77
  </AudienceModeProvider>
78
  </ThemeProvider>
79
  </body>
 
3
  import "./globals.css"
4
  import { AudienceModeProvider } from "@/components/audience-mode-provider"
5
  import { BackendRefreshListener } from "@/components/backend-refresh-listener"
6
+ import { OrgMetadataProvider } from "@/components/org-metadata-provider"
7
  import { QuickStartProvider } from "@/components/quick-start"
8
  import { SiteFooter } from "@/components/site-footer"
9
  import { ThemeProvider } from "@/components/theme-provider"
10
+ import { getOrganizationsData } from "@/lib/data-backend"
11
 
12
  const SITE_URL = "https://evalcards.evalevalai.com"
13
  const SITE_NAME = "Evaluation Cards"
 
61
  },
62
  }
63
 
64
+ export default async function RootLayout({
65
  children,
66
  }: Readonly<{
67
  children: React.ReactNode
68
  }>) {
69
+ // Per-org registry metadata (homepage url, logo, stable canonical id) for the
70
+ // whole app. Small + cached; degrades to {} when the snapshot lacks the
71
+ // organizations sidecar. Drives rename-stable evaluator URLs (slug = id).
72
+ const orgMetadata = await getOrganizationsData().catch(() => ({}))
73
  return (
74
  <html lang="en" className="antialiased" suppressHydrationWarning>
75
  <body className="font-sans">
76
  <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
77
  <AudienceModeProvider>
78
+ <OrgMetadataProvider value={orgMetadata}>
79
+ <QuickStartProvider>
80
+ <BackendRefreshListener />
81
+ {children}
82
+ <SiteFooter />
83
+ </QuickStartProvider>
84
+ </OrgMetadataProvider>
85
  </AudienceModeProvider>
86
  </ThemeProvider>
87
  </body>
components/eval-card.tsx CHANGED
@@ -19,7 +19,8 @@ import {
19
  } from "lucide-react"
20
  import Link from "next/link"
21
  import { routeIdToPath } from "@/lib/utils"
22
- import { evaluatorSlug, isRecognizedEvaluator } from "@/lib/evaluators"
 
23
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
24
  import { getTagColor, tagLabel } from "@/lib/benchmark-schema"
25
 
@@ -58,6 +59,7 @@ interface EvalCardProps {
58
 
59
  export function EvalCard({ summary, delayMs = 0 }: EvalCardProps) {
60
  const router = useRouter()
 
61
  const { mode } = useAudienceMode()
62
  const isResearchView = mode === "research"
63
  const scorePercent = `${Math.round(summary.avg_score_norm * 100)}%`
@@ -272,7 +274,7 @@ export function EvalCard({ summary, delayMs = 0 }: EvalCardProps) {
272
  <span key={name} className="inline-flex items-center">
273
  {i > 0 ? ", " : null}
274
  <Link
275
- href={`/evaluators/${evaluatorSlug(name)}`}
276
  className="hover:text-[color:var(--accent)] hover:underline"
277
  onClick={(e) => e.stopPropagation()}
278
  >
 
19
  } from "lucide-react"
20
  import Link from "next/link"
21
  import { routeIdToPath } from "@/lib/utils"
22
+ import { useEvaluatorSlug } from "@/components/org-metadata-provider"
23
+ import { isRecognizedEvaluator } from "@/lib/evaluators"
24
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
25
  import { getTagColor, tagLabel } from "@/lib/benchmark-schema"
26
 
 
59
 
60
  export function EvalCard({ summary, delayMs = 0 }: EvalCardProps) {
61
  const router = useRouter()
62
+ const slugFor = useEvaluatorSlug()
63
  const { mode } = useAudienceMode()
64
  const isResearchView = mode === "research"
65
  const scorePercent = `${Math.round(summary.avg_score_norm * 100)}%`
 
274
  <span key={name} className="inline-flex items-center">
275
  {i > 0 ? ", " : null}
276
  <Link
277
+ href={`/evaluators/${slugFor(name)}`}
278
  className="hover:text-[color:var(--accent)] hover:underline"
279
  onClick={(e) => e.stopPropagation()}
280
  >
components/eval-detail.tsx CHANGED
@@ -54,7 +54,8 @@ import {
54
  import type { BenchmarkCard, SourceData } from "@/lib/benchmark-schema"
55
  import { tagLabel } from "@/lib/benchmark-schema"
56
  import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
57
- import { evaluatorSlug, isRecognizedEvaluator } from "@/lib/evaluators"
 
58
  import type { ComparisonIndex, EvalHierarchy } from "@/lib/backend-artifacts"
59
  import type { HierarchyEvalLocation } from "@/lib/hierarchy-lookup"
60
  import { PolicyOverview } from "@/components/policy-overview"
@@ -425,12 +426,13 @@ function EvaluatorName({
425
  className?: string
426
  style?: React.CSSProperties
427
  }) {
 
428
  if (!linkName) {
429
  return <span className={className} style={style}>{display}</span>
430
  }
431
  return (
432
  <Link
433
- href={`/evaluators/${evaluatorSlug(linkName)}`}
434
  className={cn("hover:text-[color:var(--accent)] hover:underline", className)}
435
  style={style}
436
  onClick={(e) => e.stopPropagation()}
 
54
  import type { BenchmarkCard, SourceData } from "@/lib/benchmark-schema"
55
  import { tagLabel } from "@/lib/benchmark-schema"
56
  import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
57
+ import { isRecognizedEvaluator } from "@/lib/evaluators"
58
+ import { useEvaluatorSlug } from "@/components/org-metadata-provider"
59
  import type { ComparisonIndex, EvalHierarchy } from "@/lib/backend-artifacts"
60
  import type { HierarchyEvalLocation } from "@/lib/hierarchy-lookup"
61
  import { PolicyOverview } from "@/components/policy-overview"
 
426
  className?: string
427
  style?: React.CSSProperties
428
  }) {
429
+ const slugFor = useEvaluatorSlug()
430
  if (!linkName) {
431
  return <span className={className} style={style}>{display}</span>
432
  }
433
  return (
434
  <Link
435
+ href={`/evaluators/${slugFor(linkName)}`}
436
  className={cn("hover:text-[color:var(--accent)] hover:underline", className)}
437
  style={style}
438
  onClick={(e) => e.stopPropagation()}
components/evaluator/evaluator-letterhead.tsx ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { ExternalLink } from "lucide-react"
4
+
5
+ import { OrgLogo } from "@/components/evaluator/org-logo"
6
+ import { VerifiedBadge } from "@/components/signals/verified-badge"
7
+ import { cn } from "@/lib/utils"
8
+
9
+ /**
10
+ * EvaluatorLetterhead — the masthead header for an evaluator (reporting org)
11
+ * page. A full-bleed warm band with a hairline base rule, set like an academic
12
+ * journal letterhead:
13
+ *
14
+ * eyebrow ("Evaluator")
15
+ * [ logo plate ] Org Name ✓ ← vertically-centred hero row
16
+ * reporting organisation · N evaluations · N families · N verified
17
+ *
18
+ * The logo is rendered through <OrgLogo/>, which auto-squares and auto-sizes
19
+ * any aspect ratio onto a consistent light plate and falls back to a monogram
20
+ * when no mark is known. The meta strip is indented to begin under the name
21
+ * (plate width + gap) so the lockup reads as one unit.
22
+ *
23
+ * Full bleed: the negative margin matches the evaluator page's responsive
24
+ * padding (px-4 → sm:px-8) so the band paints to the content-column edges
25
+ * without causing horizontal overflow at any width.
26
+ */
27
+
28
+ export interface EvaluatorLetterheadProps {
29
+ name: string
30
+ /** Brand-mark URL/path or null → monogram. Sourced from the registry org
31
+ * metadata (organizations.json sidecar). */
32
+ logoSrc: string | null
33
+ /** Org homepage (registry `website`). When present, a small external-link
34
+ * affordance is shown next to the name; hidden entirely when absent. */
35
+ homepageUrl?: string
36
+ isVerified: boolean
37
+ recognized: boolean
38
+ evalCount: number
39
+ familyCount: number
40
+ verifiedCount: number
41
+ }
42
+
43
+ export default function EvaluatorLetterhead({
44
+ name,
45
+ logoSrc,
46
+ homepageUrl,
47
+ isVerified,
48
+ recognized,
49
+ evalCount,
50
+ familyCount,
51
+ verifiedCount,
52
+ }: EvaluatorLetterheadProps) {
53
+ return (
54
+ <header
55
+ className={cn(
56
+ "-mx-4 mb-8 border-b border-[color:var(--border-soft)] bg-[color:var(--bg-warm)] px-4",
57
+ "sm:-mx-8 sm:px-8",
58
+ )}
59
+ >
60
+ <div className="py-8">
61
+ {logoSrc === null && <span className="sr-only">{name} (no logo available)</span>}
62
+
63
+ <div className="kicker">Evaluator</div>
64
+
65
+ {/* Hero row — plate + name vertically centred together. */}
66
+ <div className="mt-2.5 flex items-center gap-5 sm:gap-6">
67
+ <OrgLogo
68
+ name={name}
69
+ src={logoSrc}
70
+ className="[--logo-size:64px] sm:[--logo-size:72px]"
71
+ />
72
+
73
+ <h1 className="ec-page-h1 inline-flex flex-wrap items-center gap-x-2.5 gap-y-1">
74
+ <span className="min-w-0 break-words">{name}</span>
75
+ <VerifiedBadge verified={isVerified} recognized={recognized} size="md" />
76
+ {homepageUrl && (
77
+ <a
78
+ href={homepageUrl}
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ aria-label={`Visit ${name}'s homepage`}
82
+ title={`Visit ${name}'s homepage`}
83
+ className="inline-flex shrink-0 items-center text-[color:var(--fg-subtle)] transition-colors hover:text-[color:var(--accent)]"
84
+ >
85
+ <ExternalLink className="h-4 w-4" aria-hidden />
86
+ </a>
87
+ )}
88
+ </h1>
89
+ </div>
90
+
91
+ {/* Mono-caps colophon — indented to begin under the name. */}
92
+ <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 pl-[84px] font-mono text-[11px] uppercase tracking-[0.12em] text-[color:var(--fg-muted)] sm:pl-[96px]">
93
+ <span>Reporting organisation</span>
94
+ <Dot />
95
+ <span>
96
+ <span className="font-semibold tabular-nums text-[color:var(--fg)]">
97
+ {evalCount.toLocaleString()}
98
+ </span>{" "}
99
+ {evalCount === 1 ? "evaluation" : "evaluations"}
100
+ </span>
101
+ <Dot />
102
+ <span>
103
+ <span className="font-semibold tabular-nums text-[color:var(--fg)]">
104
+ {familyCount.toLocaleString()}
105
+ </span>{" "}
106
+ {familyCount === 1 ? "family" : "families"}
107
+ </span>
108
+ <Dot />
109
+ <span>
110
+ <span className="font-semibold tabular-nums text-[color:var(--fg)]">
111
+ {verifiedCount.toLocaleString()}
112
+ </span>{" "}
113
+ verified
114
+ </span>
115
+ </div>
116
+ </div>
117
+ </header>
118
+ )
119
+ }
120
+
121
+ function Dot() {
122
+ return (
123
+ <span aria-hidden="true" className="text-[color:var(--fg-subtle)]">
124
+ ·
125
+ </span>
126
+ )
127
+ }
components/evaluator/org-logo.tsx ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useCallback, useEffect, useRef, useState } from "react"
4
+
5
+ import { monogramFor, monogramHue } from "@/lib/evaluator-logo"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ /**
9
+ * OrgLogo — renders an evaluating org's brand mark inside a consistent square
10
+ * plate, robust to the wildly inconsistent assets real orgs ship.
11
+ *
12
+ * The problem: some orgs have PR-ready square icons; most don't. We get 4:1
13
+ * horizontal wordmarks, tall lockups, tiny favicons, transparent PNGs and SVGs
14
+ * — every aspect ratio and resolution. We want a single, consistent on-page
15
+ * footprint regardless.
16
+ *
17
+ * The strategy ("auto-square + auto-size"):
18
+ * 1. Fixed SQUARE plate. Size is driven by the `--logo-size` CSS var (default
19
+ * 72px) so a caller can make it responsive with one arbitrary class, e.g.
20
+ * `className="[--logo-size:64px] sm:[--logo-size:72px]"`. The mark never
21
+ * changes the plate's shape.
22
+ * 2. `object-contain`, always — the mark is scaled to FIT, never cropped
23
+ * (object-cover) and never stretched (object-fill). No distortion, ever.
24
+ * 3. Aspect-aware bounds. With object-contain alone, a wide wordmark shrinks
25
+ * to a tiny sliver in a square box. So we measure the loaded image's
26
+ * natural aspect and cap width/height per shape class (wide / tall /
27
+ * square) so each reaches a comparable OPTICAL size: a wordmark is allowed
28
+ * to span most of the plate width, a square icon keeps breathing room.
29
+ * 4. Graceful fallbacks: no src, or a load error, or a degenerate 0×0 asset
30
+ * → a deterministic monogram tile on the same plate. The layout never
31
+ * collapses or shifts.
32
+ *
33
+ * Measuring handles cached images too (onLoad may not fire) via a ref check on
34
+ * mount. The mark fades in once measured to avoid a first-paint size jump.
35
+ */
36
+
37
+ type Fit = "square" | "wide" | "tall"
38
+
39
+ // Max width/height the mark may occupy inside the plate, as a fraction, per
40
+ // shape. Tuned so a 3.7:1 wordmark and a 1:1 icon feel like the same "weight".
41
+ const BOUNDS: Record<Fit, { maxW: number; maxH: number }> = {
42
+ square: { maxW: 0.64, maxH: 0.64 },
43
+ wide: { maxW: 0.9, maxH: 0.62 },
44
+ tall: { maxW: 0.62, maxH: 0.86 },
45
+ }
46
+
47
+ function classifyAspect(aspect: number): Fit {
48
+ if (!Number.isFinite(aspect) || aspect <= 0) return "square"
49
+ if (aspect > 1.3) return "wide"
50
+ if (aspect < 0.77) return "tall"
51
+ return "square"
52
+ }
53
+
54
+ export function OrgLogo({
55
+ name,
56
+ src,
57
+ className,
58
+ rounded = "rounded-[4px]",
59
+ }: {
60
+ name: string
61
+ /** Brand mark URL, or null when none is known → monogram. */
62
+ src: string | null
63
+ /** Box sizing / `--logo-size` override, e.g. "[--logo-size:64px] sm:[--logo-size:72px]". */
64
+ className?: string
65
+ rounded?: string
66
+ }) {
67
+ // "pending" until the image loads & is measured; "ok" once we have an aspect;
68
+ // "error" if it fails / is degenerate → monogram.
69
+ const [status, setStatus] = useState<"pending" | "ok" | "error">(src ? "pending" : "error")
70
+ const [fit, setFit] = useState<Fit>("square")
71
+ const imgRef = useRef<HTMLImageElement | null>(null)
72
+
73
+ const measure = useCallback((img: HTMLImageElement) => {
74
+ const { naturalWidth: w, naturalHeight: h } = img
75
+ if (!w || !h) {
76
+ setStatus("error") // degenerate asset → monogram
77
+ return
78
+ }
79
+ setFit(classifyAspect(w / h))
80
+ setStatus("ok")
81
+ }, [])
82
+
83
+ // Cached images can be `complete` before React attaches onLoad — measure on
84
+ // mount if so. Re-runs when src changes.
85
+ useEffect(() => {
86
+ if (!src) {
87
+ setStatus("error")
88
+ return
89
+ }
90
+ setStatus("pending")
91
+ const img = imgRef.current
92
+ if (img && img.complete) {
93
+ if (img.naturalWidth) measure(img)
94
+ else setStatus("error")
95
+ }
96
+ }, [src, measure])
97
+
98
+ const plateClass = cn(
99
+ "relative flex shrink-0 items-center justify-center overflow-hidden border",
100
+ "h-[var(--logo-size,72px)] w-[var(--logo-size,72px)] bg-[color:var(--logo-plate)] shadow-[var(--shadow-card)]",
101
+ rounded,
102
+ className,
103
+ )
104
+ const plateBorder = { borderColor: "var(--border-soft)" } as const
105
+
106
+ if (status === "error") {
107
+ return (
108
+ <div className={plateClass} style={plateBorder}>
109
+ <Monogram name={name} />
110
+ </div>
111
+ )
112
+ }
113
+
114
+ const { maxW, maxH } = BOUNDS[fit]
115
+ return (
116
+ <div className={plateClass} style={plateBorder}>
117
+ <img
118
+ ref={imgRef}
119
+ src={src ?? undefined}
120
+ alt={`${name} logo`}
121
+ loading="eager"
122
+ decoding="async"
123
+ onLoad={(e) => measure(e.currentTarget)}
124
+ onError={() => setStatus("error")}
125
+ className={cn(
126
+ "object-contain transition-opacity duration-200",
127
+ status === "pending" ? "opacity-0" : "opacity-100",
128
+ )}
129
+ style={{ maxWidth: `${maxW * 100}%`, maxHeight: `${maxH * 100}%` }}
130
+ />
131
+ </div>
132
+ )
133
+ }
134
+
135
+ /** Deterministic monogram tile — fills the plate; org name carried by an
136
+ * adjacent heading, so the glyph itself is aria-hidden. */
137
+ function Monogram({ name }: { name: string }) {
138
+ const hue = monogramHue(name)
139
+ return (
140
+ <span
141
+ aria-hidden="true"
142
+ className="flex h-full w-full select-none items-center justify-center rounded-[3px] font-sans font-semibold leading-none"
143
+ style={{
144
+ color: `hsl(${hue} 32% 38%)`,
145
+ backgroundColor: `hsl(${hue} 38% 92%)`,
146
+ fontSize: "calc(var(--logo-size, 72px) * 0.34)",
147
+ }}
148
+ >
149
+ {monogramFor(name)}
150
+ <span className="sr-only">{name}</span>
151
+ </span>
152
+ )
153
+ }
components/model-compare-dialog.tsx CHANGED
@@ -24,7 +24,8 @@ import {
24
  } from "@/components/ui/dialog"
25
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
26
 
27
- function formatDate(isoString: string) {
 
28
  const numeric = Number(isoString)
29
  const parsedDate =
30
  !Number.isNaN(numeric) && !isoString.includes("-")
@@ -430,7 +431,7 @@ export function ModelCompareDialog({
430
  {row.key === "latest" ? (
431
  <div className="flex items-center gap-2">
432
  <span>{model.latest_source_name || `${model.benchmarks_count} benchmark composites summarized`}</span>
433
- {model.source_urls[0] ? (
434
  <a
435
  href={model.source_urls[0]}
436
  target="_blank"
 
24
  } from "@/components/ui/dialog"
25
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
26
 
27
+ function formatDate(isoString: string | null | undefined) {
28
+ if (!isoString) return "—"
29
  const numeric = Number(isoString)
30
  const parsedDate =
31
  !Number.isNaN(numeric) && !isoString.includes("-")
 
431
  {row.key === "latest" ? (
432
  <div className="flex items-center gap-2">
433
  <span>{model.latest_source_name || `${model.benchmarks_count} benchmark composites summarized`}</span>
434
+ {model.source_urls?.[0] ? (
435
  <a
436
  href={model.source_urls[0]}
437
  target="_blank"
components/org-metadata-provider.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { createContext, useContext, useMemo } from "react"
4
+
5
+ import { evaluatorSlugFor, type OrgMetaMap } from "@/lib/evaluators"
6
+
7
+ /**
8
+ * Makes the per-org registry metadata (from the organizations.json sidecar)
9
+ * available app-wide, keyed by normalizeOrgKey(displayName). It carries each
10
+ * org's stable canonical `id`, which is what evaluator URLs are slugged from —
11
+ * so renaming an org's display name never changes its /evaluators/<slug> URL.
12
+ *
13
+ * Fetched once server-side in the root layout and handed down as a plain
14
+ * (serializable) object, so the slug is correct on first render with no flash.
15
+ */
16
+ const OrgMetadataContext = createContext<OrgMetaMap>({})
17
+
18
+ export function OrgMetadataProvider({
19
+ value,
20
+ children,
21
+ }: {
22
+ value: OrgMetaMap
23
+ children: React.ReactNode
24
+ }) {
25
+ return <OrgMetadataContext.Provider value={value ?? {}}>{children}</OrgMetadataContext.Provider>
26
+ }
27
+
28
+ /** Raw display→metadata map (homepage url, logo, canonical id). */
29
+ export function useOrgMetadata(): OrgMetaMap {
30
+ return useContext(OrgMetadataContext)
31
+ }
32
+
33
+ /**
34
+ * Returns the canonical, rename-stable slug function bound to the current org
35
+ * metadata. Use this everywhere an /evaluators/<slug> link is built so the
36
+ * whole app agrees on one URL per org.
37
+ */
38
+ export function useEvaluatorSlug(): (name: string) => string {
39
+ const orgMeta = useContext(OrgMetadataContext)
40
+ return useMemo(() => (name: string) => evaluatorSlugFor(name, orgMeta), [orgMeta])
41
+ }
lib/backend-artifacts.ts CHANGED
@@ -525,3 +525,30 @@ export interface PeerRanksSidecar {
525
  config_version: number
526
  ranks: PeerRanksMap
527
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  config_version: number
526
  ranks: PeerRanksMap
527
  }
528
+
529
+ // ---------------------------------------------------------------------------
530
+ // organizations.json — per-evaluator-org metadata (homepage URL + logo)
531
+ // ---------------------------------------------------------------------------
532
+
533
+ /** Metadata for one reporting org, sourced from the registry (canonical_orgs).
534
+ * All fields beyond `name` are optional — an org only appears here when it
535
+ * carries a website or logo_url, so the evaluator page degrades gracefully
536
+ * (monogram, no link) for everyone absent. */
537
+ export interface OrgMetadata {
538
+ /** Canonical display name (matches an `evaluator_names` string). */
539
+ name: string
540
+ /** Canonical org id, when resolved. */
541
+ id?: string
542
+ /** Homepage URL (registry `website`). */
543
+ url?: string
544
+ /** Brand-mark pointer (registry `logo_url`): frontend-relative path or URL. */
545
+ logo?: string
546
+ }
547
+
548
+ /** organizations.json envelope. `orgs` is keyed by the NORMALISED display name
549
+ * (lower-cased, whitespace-collapsed — see normalizeOrgKey) so the evaluator
550
+ * page can look an org up directly from the name it already holds. */
551
+ export interface OrgMetadataIndex {
552
+ generated_at: string
553
+ orgs: Record<string, OrgMetadata>
554
+ }
lib/dashboard-data-client.ts CHANGED
@@ -3,6 +3,7 @@ import type {
3
  ComparisonIndex,
4
  CorpusAggregates,
5
  EvalHierarchy,
 
6
  PeerRanksMap,
7
  } from "@/lib/backend-artifacts"
8
  import { decorateHierarchyDerivedTags } from "@/lib/benchmark-tags"
@@ -114,3 +115,7 @@ export function fetchCorpusAggregates() {
114
  export function fetchPeerRanks() {
115
  return fetchJson<PeerRanksMap>("/api/peer-ranks")
116
  }
 
 
 
 
 
3
  ComparisonIndex,
4
  CorpusAggregates,
5
  EvalHierarchy,
6
+ OrgMetadata,
7
  PeerRanksMap,
8
  } from "@/lib/backend-artifacts"
9
  import { decorateHierarchyDerivedTags } from "@/lib/benchmark-tags"
 
115
  export function fetchPeerRanks() {
116
  return fetchJson<PeerRanksMap>("/api/peer-ranks")
117
  }
118
+
119
+ export function fetchOrganizations() {
120
+ return fetchJson<Record<string, OrgMetadata>>("/api/org-metadata")
121
+ }
lib/data-backend.ts CHANGED
@@ -90,6 +90,10 @@ export async function getBackendManifestStatusData(): Promise<BackendManifestSta
90
  }
91
  }
92
 
 
 
 
 
93
  export async function getEvalHierarchyData() {
94
  // The v2 sidecar ships hierarchy.json in the composite/family/slice
95
  // taxonomy shape (top-level `composites[]`, flat `families[]` lookup
 
90
  }
91
  }
92
 
93
+ export async function getOrganizationsData() {
94
+ return (await sidecars()).fetchOrganizations()
95
+ }
96
+
97
  export async function getEvalHierarchyData() {
98
  // The v2 sidecar ships hierarchy.json in the composite/family/slice
99
  // taxonomy shape (top-level `composites[]`, flat `families[]` lookup
lib/evaluator-logo.ts ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Org-logo helpers for the evaluator page.
3
+ *
4
+ * Logos + homepage URLs are sourced from the registry (canonical_orgs) and
5
+ * delivered to the frontend via the organizations.json sidecar — see
6
+ * lib/dashboard-data-client.fetchOrganizations(). The evaluator page looks an
7
+ * org up by normalizeOrgKey(name); the mark is rendered through <OrgLogo/>,
8
+ * which auto-squares + auto-sizes any aspect ratio and falls back to the
9
+ * name-derived monogram when the registry has no logo for the org.
10
+ */
11
+
12
+ /**
13
+ * Monogram initials for the logo-less fallback. Drops common org-suffix noise
14
+ * ("AI", "Institute", "Inc"…), takes the first letter of up to two significant
15
+ * words, and falls back to the first two characters for single-word names.
16
+ */
17
+ const STOP_WORDS = new Set([
18
+ "ai",
19
+ "the",
20
+ "of",
21
+ "for",
22
+ "and",
23
+ "institute",
24
+ "inc",
25
+ "labs",
26
+ "lab",
27
+ "research",
28
+ ])
29
+
30
+ export function monogramFor(name: string): string {
31
+ const words = name
32
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
33
+ .split(/\s+/)
34
+ .filter(Boolean)
35
+ const significant = words.filter((w) => !STOP_WORDS.has(w.toLowerCase()))
36
+ const pool = significant.length > 0 ? significant : words
37
+ if (pool.length === 0) return "?"
38
+ if (pool.length === 1) {
39
+ return pool[0].slice(0, 2).toUpperCase()
40
+ }
41
+ return (pool[0][0] + pool[1][0]).toUpperCase()
42
+ }
43
+
44
+ /**
45
+ * Deterministic warm hue for a monogram tile, derived from the name so the
46
+ * same org always gets the same colour. Tuned to sit quietly against the warm
47
+ * neutral palette (low saturation, mid lightness).
48
+ */
49
+ export function monogramHue(name: string): number {
50
+ let h = 0
51
+ for (let i = 0; i < name.length; i++) {
52
+ h = (h * 31 + name.charCodeAt(i)) % 360
53
+ }
54
+ return h
55
+ }
56
+
57
+ /**
58
+ * Normalise an org/evaluator name to the key used by the organizations.json
59
+ * sidecar. MUST stay in sync with the pipeline's `_normalize_org_key`
60
+ * (eval_cards_backend_pipeline/.../sidecars.py) so the frontend's lookup keys
61
+ * match the producer's. The evaluator page uses this to find an org's homepage
62
+ * URL + logo from the sidecar map.
63
+ */
64
+ export function normalizeOrgKey(name: string): string {
65
+ return name.trim().toLowerCase().replace(/\s+/g, " ")
66
+ }
lib/evaluators.ts CHANGED
@@ -17,7 +17,13 @@
17
  * 2025)") still produce a valid, unique, non-empty slug.
18
  */
19
 
 
20
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
 
 
 
 
 
21
 
22
  /**
23
  * "Recognized" evaluator orgs — public leaderboards whose results we ingest
@@ -75,6 +81,23 @@ export function evaluatorSlug(name: string): string {
75
  return slug || "evaluator"
76
  }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  /** @deprecated internal alias — use {@link evaluatorSlug}. */
79
  const baseSlug = evaluatorSlug
80
 
@@ -84,7 +107,10 @@ const baseSlug = evaluatorSlug
84
  * order (name-sorted) so the same corpus always yields the same slugs;
85
  * collisions get a numeric suffix (`-2`, `-3`, …).
86
  */
87
- export function buildEvaluatorSlugMap(evals: BenchmarkEvalListItem[]): {
 
 
 
88
  slugToName: Map<string, string>
89
  nameToSlug: Map<string, string>
90
  } {
@@ -101,7 +127,7 @@ export function buildEvaluatorSlugMap(evals: BenchmarkEvalListItem[]): {
101
  const used = new Map<string, number>()
102
 
103
  for (const name of Array.from(names).sort((a, b) => a.localeCompare(b))) {
104
- const base = baseSlug(name)
105
  const seen = used.get(base) ?? 0
106
  const slug = seen === 0 ? base : `${base}-${seen + 1}`
107
  used.set(base, seen + 1)
@@ -120,10 +146,10 @@ export function buildEvaluatorSlugMap(evals: BenchmarkEvalListItem[]): {
120
  */
121
  export function groupEvalsByEvaluator(
122
  evals: BenchmarkEvalListItem[],
123
- opts?: { verifiedOnly?: boolean },
124
  ): EvaluatorGroup[] {
125
  const verifiedOnly = opts?.verifiedOnly ?? false
126
- const { nameToSlug } = buildEvaluatorSlugMap(evals)
127
 
128
  const acc = new Map<string, { evalCount: number; verifiedCount: number; isVerified: boolean }>()
129
 
@@ -159,7 +185,7 @@ export function groupEvalsByEvaluator(
159
  for (const [name, v] of acc) {
160
  groups.push({
161
  name,
162
- slug: nameToSlug.get(name) ?? baseSlug(name),
163
  evalCount: v.evalCount,
164
  verifiedCount: v.verifiedCount,
165
  isVerified: v.isVerified,
@@ -179,10 +205,10 @@ export function groupEvalsByEvaluator(
179
  export function getEvalsForEvaluator(
180
  allEvals: BenchmarkEvalListItem[],
181
  slug: string,
182
- opts?: { verifiedOnly?: boolean },
183
  ): { name: string | null; isVerified: boolean; evals: BenchmarkEvalListItem[] } {
184
  const verifiedOnly = opts?.verifiedOnly ?? false
185
- const { slugToName } = buildEvaluatorSlugMap(allEvals)
186
  // Direct hit on the (possibly collision-suffixed) full slug, else fall back
187
  // to base-slug matching so links built from the shared `evaluatorSlug(name)`
188
  // helper — which can't see the suffix step — still resolve. The fallback is
@@ -192,7 +218,7 @@ export function getEvalsForEvaluator(
192
  for (const [, candidate] of Array.from(slugToName.entries()).sort((a, b) =>
193
  a[1].localeCompare(b[1]),
194
  )) {
195
- if (evaluatorSlug(candidate) === slug) {
196
  name = candidate
197
  break
198
  }
 
17
  * 2025)") still produce a valid, unique, non-empty slug.
18
  */
19
 
20
+ import type { OrgMetadata } from "@/lib/backend-artifacts"
21
  import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
22
+ import { normalizeOrgKey } from "@/lib/evaluator-logo"
23
+
24
+ /** Display-name → registry org metadata (incl. the stable canonical `id`),
25
+ * as delivered by the organizations.json sidecar (keyed by normalizeOrgKey). */
26
+ export type OrgMetaMap = Record<string, OrgMetadata>
27
 
28
  /**
29
  * "Recognized" evaluator orgs — public leaderboards whose results we ingest
 
81
  return slug || "evaluator"
82
  }
83
 
84
+ /**
85
+ * Stable, URL-safe slug for an evaluator — the SINGLE source of truth for
86
+ * /evaluators/<slug> URLs (every link builder and the detail-route resolver
87
+ * go through this).
88
+ *
89
+ * When the org resolves to a registry entity (`orgMeta` carries its canonical
90
+ * `id`), the slug is derived from that STABLE id, so renaming the display name
91
+ * never changes the URL. Orgs with no registry entry (raw corpus strings,
92
+ * which can't be renamed) fall back to the display-name slug. `evaluatorSlug`
93
+ * is applied to the id too so the slug stays lowercase/url-safe regardless of
94
+ * id casing (e.g. id "TIGER-Lab" → "tiger-lab").
95
+ */
96
+ export function evaluatorSlugFor(name: string, orgMeta?: OrgMetaMap): string {
97
+ const id = orgMeta?.[normalizeOrgKey(name)]?.id
98
+ return id && id.trim() ? evaluatorSlug(id) : evaluatorSlug(name)
99
+ }
100
+
101
  /** @deprecated internal alias — use {@link evaluatorSlug}. */
102
  const baseSlug = evaluatorSlug
103
 
 
107
  * order (name-sorted) so the same corpus always yields the same slugs;
108
  * collisions get a numeric suffix (`-2`, `-3`, …).
109
  */
110
+ export function buildEvaluatorSlugMap(
111
+ evals: BenchmarkEvalListItem[],
112
+ orgMeta?: OrgMetaMap,
113
+ ): {
114
  slugToName: Map<string, string>
115
  nameToSlug: Map<string, string>
116
  } {
 
127
  const used = new Map<string, number>()
128
 
129
  for (const name of Array.from(names).sort((a, b) => a.localeCompare(b))) {
130
+ const base = evaluatorSlugFor(name, orgMeta)
131
  const seen = used.get(base) ?? 0
132
  const slug = seen === 0 ? base : `${base}-${seen + 1}`
133
  used.set(base, seen + 1)
 
146
  */
147
  export function groupEvalsByEvaluator(
148
  evals: BenchmarkEvalListItem[],
149
+ opts?: { verifiedOnly?: boolean; orgMeta?: OrgMetaMap },
150
  ): EvaluatorGroup[] {
151
  const verifiedOnly = opts?.verifiedOnly ?? false
152
+ const { nameToSlug } = buildEvaluatorSlugMap(evals, opts?.orgMeta)
153
 
154
  const acc = new Map<string, { evalCount: number; verifiedCount: number; isVerified: boolean }>()
155
 
 
185
  for (const [name, v] of acc) {
186
  groups.push({
187
  name,
188
+ slug: nameToSlug.get(name) ?? evaluatorSlugFor(name, opts?.orgMeta),
189
  evalCount: v.evalCount,
190
  verifiedCount: v.verifiedCount,
191
  isVerified: v.isVerified,
 
205
  export function getEvalsForEvaluator(
206
  allEvals: BenchmarkEvalListItem[],
207
  slug: string,
208
+ opts?: { verifiedOnly?: boolean; orgMeta?: OrgMetaMap },
209
  ): { name: string | null; isVerified: boolean; evals: BenchmarkEvalListItem[] } {
210
  const verifiedOnly = opts?.verifiedOnly ?? false
211
+ const { slugToName } = buildEvaluatorSlugMap(allEvals, opts?.orgMeta)
212
  // Direct hit on the (possibly collision-suffixed) full slug, else fall back
213
  // to base-slug matching so links built from the shared `evaluatorSlug(name)`
214
  // helper — which can't see the suffix step — still resolve. The fallback is
 
218
  for (const [, candidate] of Array.from(slugToName.entries()).sort((a, b) =>
219
  a[1].localeCompare(b[1]),
220
  )) {
221
+ if (evaluatorSlugFor(candidate, opts?.orgMeta) === slug) {
222
  name = candidate
223
  break
224
  }
lib/sidecars.ts CHANGED
@@ -18,6 +18,8 @@ import type {
18
  ComparisonIndex,
19
  CorpusAggregates,
20
  EvalHierarchy,
 
 
21
  PeerRanksMap,
22
  PeerRanksSidecar,
23
  } from "@/lib/backend-artifacts"
@@ -35,6 +37,7 @@ let cache: {
35
  hierarchy?: CacheSlot<EvalHierarchy>
36
  comparisonIndex?: CacheSlot<ComparisonIndex>
37
  peerRanks?: CacheSlot<PeerRanksMap>
 
38
  } = {}
39
 
40
  function getSnapshotUrl() {
@@ -378,6 +381,30 @@ export function fetchPeerRanks(): Promise<PeerRanksMap> {
378
  )
379
  }
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  /**
382
  * Fail fast on contract regressions. Comparison-index rows must carry
383
  * `family_id`; the model-page graph view collapses without it.
 
18
  ComparisonIndex,
19
  CorpusAggregates,
20
  EvalHierarchy,
21
+ OrgMetadata,
22
+ OrgMetadataIndex,
23
  PeerRanksMap,
24
  PeerRanksSidecar,
25
  } from "@/lib/backend-artifacts"
 
37
  hierarchy?: CacheSlot<EvalHierarchy>
38
  comparisonIndex?: CacheSlot<ComparisonIndex>
39
  peerRanks?: CacheSlot<PeerRanksMap>
40
+ organizations?: CacheSlot<Record<string, OrgMetadata>>
41
  } = {}
42
 
43
  function getSnapshotUrl() {
 
381
  )
382
  }
383
 
384
+ /**
385
+ * Per-evaluator-org metadata (homepage URL + logo pointer) from
386
+ * `warehouse/<snapshot>/organizations.json`, sourced from the registry.
387
+ * Resolves to the bare `normalizedName -> OrgMetadata` map the evaluator page
388
+ * looks up by name.
389
+ *
390
+ * Returns an empty map when the snapshot doesn't carry the file (older pinned
391
+ * snapshots predate the Stage J `organizations` sidecar), logging a warning
392
+ * rather than throwing so the page still renders with monograms + no links.
393
+ */
394
+ export function fetchOrganizations(): Promise<Record<string, OrgMetadata>> {
395
+ return getCachedValue("organizations", "organizations", (preferStale) =>
396
+ fetchJson<OrgMetadataIndex>("organizations.json", preferStale)
397
+ .then((payload) => payload?.orgs ?? {})
398
+ .catch((err) => {
399
+ console.warn(
400
+ `[sidecars] organizations.json not available on snapshot; ` +
401
+ `falling back to empty map. ${err instanceof Error ? err.message : String(err)}`,
402
+ )
403
+ return {} as Record<string, OrgMetadata>
404
+ }),
405
+ )
406
+ }
407
+
408
  /**
409
  * Fail fast on contract regressions. Comparison-index rows must carry
410
  * `family_id`; the model-page graph view collapses without it.
public/org-logos/arcadia.webp ADDED

Git LFS Details

  • SHA256: b81dcfd38fe18df3807c4f23a0e8e55a9fdcd768a938402d6d4f0c19192ef117
  • Pointer size: 130 Bytes
  • Size of remote file: 39.4 kB
public/org-logos/artificial-analysis.svg ADDED
public/org-logos/exgentic.png ADDED

Git LFS Details

  • SHA256: 4a755b8f03f06d5e56b9192db6b179187bc467a6dcea51b2c52b59ccac15da58
  • Pointer size: 130 Bytes
  • Size of remote file: 38.4 kB
public/org-logos/huggingface.svg ADDED
public/org-logos/llm-stats.svg ADDED
public/org-logos/mercor.png ADDED

Git LFS Details

  • SHA256: 89e25702aff872500a20fcbeb2a64cc369deab85837f87398354c5c7c0144299
  • Pointer size: 131 Bytes
  • Size of remote file: 139 kB
public/org-logos/si-anthropic.svg ADDED
public/org-logos/si-kaggle.svg ADDED
public/org-logos/stanford-crfm.png ADDED

Git LFS Details

  • SHA256: 74391ab8bff0251fcbbaeecbee996da614b76bd1d22e30e4a552dae82f6f700e
  • Pointer size: 130 Bytes
  • Size of remote file: 62.7 kB
public/org-logos/writer.svg ADDED