Spaces:
Running
Running
format fixes - validated evals
Browse files- app/evaluators/[...id]/page.tsx +77 -68
- components/benchmark-detail.tsx +26 -5
- components/family-table.tsx +19 -5
- lib/policy-summaries.ts +17 -7
app/evaluators/[...id]/page.tsx
CHANGED
|
@@ -4,15 +4,14 @@ import { Suspense, useCallback, useEffect, useMemo, useState } from "react"
|
|
| 4 |
import { useParams, useRouter, useSearchParams } from "next/navigation"
|
| 5 |
import { ArrowLeft, Search } from "lucide-react"
|
| 6 |
|
| 7 |
-
import {
|
| 8 |
-
import { InfiniteScrollSentinel } from "@/components/infinite-scroll"
|
| 9 |
import { Navigation } from "@/components/navigation"
|
| 10 |
import { VerifiedBadge } from "@/components/signals/verified-badge"
|
| 11 |
-
import {
|
|
|
|
|
|
|
| 12 |
import type { BenchmarkEvalListItem } from "@/lib/eval-processing"
|
| 13 |
-
import { getEvalsForEvaluator } from "@/lib/evaluators"
|
| 14 |
-
|
| 15 |
-
const PAGE_SIZE = 24
|
| 16 |
|
| 17 |
function EvaluatorDetailInner() {
|
| 18 |
const params = useParams()
|
|
@@ -29,70 +28,81 @@ function EvaluatorDetailInner() {
|
|
| 29 |
}, [params.id])
|
| 30 |
|
| 31 |
const [allEvals, setAllEvals] = useState<BenchmarkEvalListItem[]>([])
|
|
|
|
|
|
|
| 32 |
const [loading, setLoading] = useState(true)
|
| 33 |
const [error, setError] = useState<string | null>(null)
|
| 34 |
const [searchQuery, setSearchQuery] = useState("")
|
| 35 |
-
const [
|
|
|
|
| 36 |
|
| 37 |
useEffect(() => {
|
| 38 |
-
fetchEvalList()
|
| 39 |
.then((list) => setAllEvals(list.evals))
|
| 40 |
.catch((err) => {
|
| 41 |
console.error(err)
|
| 42 |
setError("Failed to load evaluations")
|
| 43 |
})
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}, [])
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
const { name, isVerified, evals } = useMemo(
|
| 48 |
() => getEvalsForEvaluator(allEvals, slug, { verifiedOnly }),
|
| 49 |
[allEvals, slug, verifiedOnly],
|
| 50 |
)
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
const { familyCount, verifiedCount } = useMemo(() => {
|
| 71 |
-
const families = new Set<string>()
|
| 72 |
let verified = 0
|
| 73 |
for (const ev of evals) {
|
| 74 |
-
const fam = ev.family_display_name?.trim()
|
| 75 |
-
if (fam) families.add(fam)
|
| 76 |
if (name && (ev.verified_evaluator_names ?? []).includes(name)) verified += 1
|
| 77 |
}
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
useEffect(() => {
|
| 85 |
-
setVisibleCount(PAGE_SIZE)
|
| 86 |
-
}, [searchQuery, slug, verifiedOnly])
|
| 87 |
-
|
| 88 |
-
const visibleEvals = useMemo(
|
| 89 |
-
() => filteredEvals.slice(0, visibleCount),
|
| 90 |
-
[filteredEvals, visibleCount],
|
| 91 |
-
)
|
| 92 |
-
const hasMore = visibleCount < filteredEvals.length
|
| 93 |
-
const handleLoadMore = useCallback(() => {
|
| 94 |
-
setVisibleCount((current) => Math.min(current + PAGE_SIZE, filteredEvals.length))
|
| 95 |
-
}, [filteredEvals.length])
|
| 96 |
|
| 97 |
const handleBack = useCallback(() => {
|
| 98 |
router.push(verifiedOnly ? "/evals?groupBy=evaluator&verified=1" : "/evals?groupBy=evaluator")
|
|
@@ -164,8 +174,8 @@ function EvaluatorDetailInner() {
|
|
| 164 |
<span>{verifiedCount} verified</span>
|
| 165 |
</div>
|
| 166 |
<p className="ec-page-lede">
|
| 167 |
-
Reported <strong>{
|
| 168 |
-
{
|
| 169 |
<strong>{familyCount.toLocaleString()}</strong>{" "}
|
| 170 |
{familyCount === 1 ? "benchmark family" : "benchmark families"}
|
| 171 |
{verifiedCount > 0 && (
|
|
@@ -179,7 +189,7 @@ function EvaluatorDetailInner() {
|
|
| 179 |
<div className="ec-page-meta mt-2">
|
| 180 |
<div className="ec-page-meta-item">
|
| 181 |
<span className="ec-page-meta-item-l">Evaluations</span>
|
| 182 |
-
<span className="ec-page-meta-item-v">{
|
| 183 |
</div>
|
| 184 |
<div className="ec-page-meta-item">
|
| 185 |
<span className="ec-page-meta-item-l">Verified</span>
|
|
@@ -191,14 +201,14 @@ function EvaluatorDetailInner() {
|
|
| 191 |
</div>
|
| 192 |
</div>
|
| 193 |
|
| 194 |
-
{/*
|
| 195 |
<div className="mb-6 flex flex-wrap items-center gap-x-8 gap-y-3 border-y border-[color:var(--border-soft)] py-4">
|
| 196 |
<div className="flex shrink-0 flex-wrap items-baseline gap-x-4 gap-y-1 font-mono text-[11px] tracking-[0.1em] uppercase text-[color:var(--fg-subtle)]">
|
| 197 |
<span>
|
| 198 |
<span className="text-[color:var(--fg)] tabular-nums font-semibold mr-1">
|
| 199 |
-
{
|
| 200 |
</span>
|
| 201 |
-
{
|
| 202 |
</span>
|
| 203 |
</div>
|
| 204 |
|
|
@@ -210,30 +220,29 @@ function EvaluatorDetailInner() {
|
|
| 210 |
className="ec-input pl-9"
|
| 211 |
value={searchQuery}
|
| 212 |
onChange={(event) => setSearchQuery(event.target.value)}
|
| 213 |
-
placeholder="Search
|
| 214 |
/>
|
| 215 |
</div>
|
| 216 |
</div>
|
| 217 |
|
| 218 |
-
{/*
|
| 219 |
-
{
|
| 220 |
<div className="border border-dashed border-[color:var(--border-soft)] bg-[color:var(--bg-warm)] py-12 text-center font-mono text-[11px] uppercase tracking-[0.2em] text-[color:var(--fg-subtle)]">
|
| 221 |
No evaluations match the current filters
|
| 222 |
</div>
|
| 223 |
) : (
|
| 224 |
-
<
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
)}
|
| 230 |
-
|
| 231 |
-
<InfiniteScrollSentinel
|
| 232 |
-
hasMore={hasMore}
|
| 233 |
-
onLoadMore={handleLoadMore}
|
| 234 |
-
loadingLabel="Loading moreβ¦"
|
| 235 |
-
endLabel={`Showing ${Math.min(visibleCount, filteredEvals.length).toLocaleString()} of ${filteredEvals.length.toLocaleString()} evaluations`}
|
| 236 |
-
/>
|
| 237 |
</main>
|
| 238 |
</div>
|
| 239 |
)
|
|
|
|
| 4 |
import { useParams, useRouter, useSearchParams } 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, verifiedEvalIds } from "@/lib/evaluators"
|
|
|
|
|
|
|
| 15 |
|
| 16 |
function EvaluatorDetailInner() {
|
| 17 |
const params = useParams()
|
|
|
|
| 28 |
}, [params.id])
|
| 29 |
|
| 30 |
const [allEvals, setAllEvals] = useState<BenchmarkEvalListItem[]>([])
|
| 31 |
+
const [hierarchy, setHierarchy] = useState<EvalHierarchy | null>(null)
|
| 32 |
+
const [benchmarkCards, setBenchmarkCards] = useState<Record<string, BenchmarkCard>>({})
|
| 33 |
const [loading, setLoading] = useState(true)
|
| 34 |
const [error, setError] = useState<string | null>(null)
|
| 35 |
const [searchQuery, setSearchQuery] = useState("")
|
| 36 |
+
const [sortCol, setSortCol] = useState<FamilySortCol>("name")
|
| 37 |
+
const [sortDir, setSortDir] = useState<"asc" | "desc">("asc")
|
| 38 |
|
| 39 |
useEffect(() => {
|
| 40 |
+
const evalListRequest = fetchEvalList()
|
| 41 |
.then((list) => setAllEvals(list.evals))
|
| 42 |
.catch((err) => {
|
| 43 |
console.error(err)
|
| 44 |
setError("Failed to load evaluations")
|
| 45 |
})
|
| 46 |
+
const hierarchyRequest = fetchEvalHierarchy()
|
| 47 |
+
.then((h) => setHierarchy(h))
|
| 48 |
+
.catch(console.error)
|
| 49 |
+
const metadataRequest = fetchBenchmarkMetadata()
|
| 50 |
+
.then((metadata) => setBenchmarkCards(metadata))
|
| 51 |
+
.catch(console.error)
|
| 52 |
+
Promise.allSettled([evalListRequest, hierarchyRequest, metadataRequest]).finally(() =>
|
| 53 |
+
setLoading(false),
|
| 54 |
+
)
|
| 55 |
}, [])
|
| 56 |
|
| 57 |
+
const handleSort = useCallback((col: FamilySortCol) => {
|
| 58 |
+
if (sortCol === col) {
|
| 59 |
+
setSortDir((d) => (d === "asc" ? "desc" : "asc"))
|
| 60 |
+
} else {
|
| 61 |
+
setSortCol(col)
|
| 62 |
+
setSortDir("asc")
|
| 63 |
+
}
|
| 64 |
+
}, [sortCol])
|
| 65 |
+
|
| 66 |
const { name, isVerified, evals } = useMemo(
|
| 67 |
() => getEvalsForEvaluator(allEvals, slug, { verifiedOnly }),
|
| 68 |
[allEvals, slug, verifiedOnly],
|
| 69 |
)
|
| 70 |
|
| 71 |
+
// Eval-id universe owned by this evaluator β restricts the family tree to
|
| 72 |
+
// this org's evaluations (slices already excluded by getEvalsForEvaluator).
|
| 73 |
+
const restrictEvalIds = useMemo(() => {
|
| 74 |
+
const set = new Set<string>()
|
| 75 |
+
for (const ev of evals) set.add(ev.evaluation_id)
|
| 76 |
+
return set
|
| 77 |
+
}, [evals])
|
| 78 |
+
|
| 79 |
+
const verifiedIds = useMemo(() => verifiedEvalIds(allEvals), [allEvals])
|
| 80 |
+
|
| 81 |
+
const evalItems = useMemo(() => {
|
| 82 |
+
const map = new Map<string, BenchmarkEvalListItem>()
|
| 83 |
+
for (const ev of allEvals) map.set(ev.evaluation_id, ev)
|
| 84 |
+
return map
|
| 85 |
+
}, [allEvals])
|
| 86 |
+
|
| 87 |
+
const families = hierarchy?.families ?? []
|
| 88 |
+
|
| 89 |
+
// Quantified facts for the header. familyCount counts the top-level families
|
| 90 |
+
// the table actually renders for this org (those whose constituent evals
|
| 91 |
+
// intersect this evaluator's set), so the header agrees with the accordion
|
| 92 |
+
// below it rather than the finer family_display_name grouping.
|
| 93 |
const { familyCount, verifiedCount } = useMemo(() => {
|
|
|
|
| 94 |
let verified = 0
|
| 95 |
for (const ev of evals) {
|
|
|
|
|
|
|
| 96 |
if (name && (ev.verified_evaluator_names ?? []).includes(name)) verified += 1
|
| 97 |
}
|
| 98 |
+
let familyCount = 0
|
| 99 |
+
for (const fam of families) {
|
| 100 |
+
if ((fam.constituent_evaluation_ids ?? []).some((id) => restrictEvalIds.has(id))) {
|
| 101 |
+
familyCount += 1
|
| 102 |
+
}
|
| 103 |
}
|
| 104 |
+
return { familyCount, verifiedCount: verified }
|
| 105 |
+
}, [evals, name, families, restrictEvalIds])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
const handleBack = useCallback(() => {
|
| 108 |
router.push(verifiedOnly ? "/evals?groupBy=evaluator&verified=1" : "/evals?groupBy=evaluator")
|
|
|
|
| 174 |
<span>{verifiedCount} verified</span>
|
| 175 |
</div>
|
| 176 |
<p className="ec-page-lede">
|
| 177 |
+
Reported <strong>{evals.length.toLocaleString()}</strong>{" "}
|
| 178 |
+
{evals.length === 1 ? "evaluation" : "evaluations"} across{" "}
|
| 179 |
<strong>{familyCount.toLocaleString()}</strong>{" "}
|
| 180 |
{familyCount === 1 ? "benchmark family" : "benchmark families"}
|
| 181 |
{verifiedCount > 0 && (
|
|
|
|
| 189 |
<div className="ec-page-meta mt-2">
|
| 190 |
<div className="ec-page-meta-item">
|
| 191 |
<span className="ec-page-meta-item-l">Evaluations</span>
|
| 192 |
+
<span className="ec-page-meta-item-v">{evals.length.toLocaleString()}</span>
|
| 193 |
</div>
|
| 194 |
<div className="ec-page-meta-item">
|
| 195 |
<span className="ec-page-meta-item-l">Verified</span>
|
|
|
|
| 201 |
</div>
|
| 202 |
</div>
|
| 203 |
|
| 204 |
+
{/* FILTER BAR ---------------------------------------------- */}
|
| 205 |
<div className="mb-6 flex flex-wrap items-center gap-x-8 gap-y-3 border-y border-[color:var(--border-soft)] py-4">
|
| 206 |
<div className="flex shrink-0 flex-wrap items-baseline gap-x-4 gap-y-1 font-mono text-[11px] tracking-[0.1em] uppercase text-[color:var(--fg-subtle)]">
|
| 207 |
<span>
|
| 208 |
<span className="text-[color:var(--fg)] tabular-nums font-semibold mr-1">
|
| 209 |
+
{evals.length.toLocaleString()}
|
| 210 |
</span>
|
| 211 |
+
{evals.length === 1 ? "evaluation" : "evaluations"}
|
| 212 |
</span>
|
| 213 |
</div>
|
| 214 |
|
|
|
|
| 220 |
className="ec-input pl-9"
|
| 221 |
value={searchQuery}
|
| 222 |
onChange={(event) => setSearchQuery(event.target.value)}
|
| 223 |
+
placeholder="Search benchmarksβ¦"
|
| 224 |
/>
|
| 225 |
</div>
|
| 226 |
</div>
|
| 227 |
|
| 228 |
+
{/* FAMILY TABLE β scoped to this evaluator's evaluations ---- */}
|
| 229 |
+
{restrictEvalIds.size === 0 ? (
|
| 230 |
<div className="border border-dashed border-[color:var(--border-soft)] bg-[color:var(--bg-warm)] py-12 text-center font-mono text-[11px] uppercase tracking-[0.2em] text-[color:var(--fg-subtle)]">
|
| 231 |
No evaluations match the current filters
|
| 232 |
</div>
|
| 233 |
) : (
|
| 234 |
+
<FamilyTable
|
| 235 |
+
families={families}
|
| 236 |
+
evalItems={evalItems}
|
| 237 |
+
benchmarkCards={benchmarkCards}
|
| 238 |
+
searchQuery={searchQuery}
|
| 239 |
+
verifiedEvalIds={verifiedOnly ? verifiedIds : null}
|
| 240 |
+
restrictEvalIds={restrictEvalIds}
|
| 241 |
+
sortCol={sortCol}
|
| 242 |
+
sortDir={sortDir}
|
| 243 |
+
onSort={handleSort}
|
| 244 |
+
/>
|
| 245 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
</main>
|
| 247 |
</div>
|
| 248 |
)
|
components/benchmark-detail.tsx
CHANGED
|
@@ -1856,7 +1856,10 @@ export function BenchmarkDetail({
|
|
| 1856 |
// families. "overlaps" = cross-family duplicates only, rendered as a
|
| 1857 |
// table (no plotbox/list toggle) with mean and 95% CI for the model's
|
| 1858 |
// score across each canonical's appearances.
|
| 1859 |
-
|
|
|
|
|
|
|
|
|
|
| 1860 |
const [expandedFamilies, setExpandedFamilies] = useState<Set<string>>(new Set())
|
| 1861 |
const toggleFamily = (key: string) =>
|
| 1862 |
setExpandedFamilies((prev) => {
|
|
@@ -2257,6 +2260,7 @@ export function BenchmarkDetail({
|
|
| 2257 |
return buildModelPolicySummary({
|
| 2258 |
summary,
|
| 2259 |
thirdPartyEvaluations: reportingStats.thirdPartyEvaluations,
|
|
|
|
| 2260 |
organizationCount: reportingStats.organizationCount,
|
| 2261 |
organizationNames: reportingStats.organizationNames,
|
| 2262 |
benchmarkCount,
|
|
@@ -2264,6 +2268,7 @@ export function BenchmarkDetail({
|
|
| 2264 |
})
|
| 2265 |
}, [
|
| 2266 |
allCategoryResults,
|
|
|
|
| 2267 |
reportingStats.thirdPartyEvaluations,
|
| 2268 |
reportingStats.organizationCount,
|
| 2269 |
reportingStats.organizationNames,
|
|
@@ -2961,6 +2966,11 @@ export function BenchmarkDetail({
|
|
| 2961 |
currentModelIdentityKeys,
|
| 2962 |
])
|
| 2963 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2964 |
// Per-(eval, metric) leaderboards sourced from comparison-index.json.
|
| 2965 |
const benchmarkHistograms = useMemo<Map<string, BenchmarkHistogram>>(() => {
|
| 2966 |
const result = new Map<string, BenchmarkHistogram>()
|
|
@@ -5130,6 +5140,16 @@ export function BenchmarkDetail({
|
|
| 5130 |
</div>
|
| 5131 |
</div>
|
| 5132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5133 |
{/* SUMMARY VIEW β text-only list grouped by family, ranked
|
| 5134 |
bestβworst, with the category pill bar so non-technical
|
| 5135 |
readers can filter without diving into Researcher view. */}
|
|
@@ -5354,14 +5374,15 @@ export function BenchmarkDetail({
|
|
| 5354 |
</div>
|
| 5355 |
<div className="flex flex-wrap gap-1">
|
| 5356 |
{row.appearances.map((app) => (
|
| 5357 |
-
<
|
| 5358 |
key={`${row.canonicalKey}::${app.familyKey}::${app.evalSummaryId}`}
|
| 5359 |
-
|
|
|
|
| 5360 |
style={{ fontSize: 10 }}
|
| 5361 |
-
title={`${app.familyName} Β· ${app.metricName}`}
|
| 5362 |
>
|
| 5363 |
{app.familyName} Β· {fmt(app.score)}
|
| 5364 |
-
</
|
| 5365 |
))}
|
| 5366 |
</div>
|
| 5367 |
</div>
|
|
|
|
| 1856 |
// families. "overlaps" = cross-family duplicates only, rendered as a
|
| 1857 |
// table (no plotbox/list toggle) with mean and 95% CI for the model's
|
| 1858 |
// score across each canonical's appearances.
|
| 1859 |
+
// `null` until the user explicitly picks a view; the effective default is
|
| 1860 |
+
// derived from the data (overlaps when this model has any cross-suite
|
| 1861 |
+
// overlaps, else source) β see `groupingMode` just after `overlapsRows`.
|
| 1862 |
+
const [pickedGroupingMode, setGroupingMode] = useState<"source" | "category" | "overlaps" | null>(null)
|
| 1863 |
const [expandedFamilies, setExpandedFamilies] = useState<Set<string>>(new Set())
|
| 1864 |
const toggleFamily = (key: string) =>
|
| 1865 |
setExpandedFamilies((prev) => {
|
|
|
|
| 2260 |
return buildModelPolicySummary({
|
| 2261 |
summary,
|
| 2262 |
thirdPartyEvaluations: reportingStats.thirdPartyEvaluations,
|
| 2263 |
+
reportedEvaluationCount: allEvaluations.length,
|
| 2264 |
organizationCount: reportingStats.organizationCount,
|
| 2265 |
organizationNames: reportingStats.organizationNames,
|
| 2266 |
benchmarkCount,
|
|
|
|
| 2268 |
})
|
| 2269 |
}, [
|
| 2270 |
allCategoryResults,
|
| 2271 |
+
allEvaluations.length,
|
| 2272 |
reportingStats.thirdPartyEvaluations,
|
| 2273 |
reportingStats.organizationCount,
|
| 2274 |
reportingStats.organizationNames,
|
|
|
|
| 2966 |
currentModelIdentityKeys,
|
| 2967 |
])
|
| 2968 |
|
| 2969 |
+
// Effective view: honour the user's explicit pick; otherwise default to
|
| 2970 |
+
// overlaps when this model has cross-suite overlaps, falling back to source
|
| 2971 |
+
// when it has none (so models without overlaps don't open on an empty view).
|
| 2972 |
+
const groupingMode = pickedGroupingMode ?? (overlapsRows.length > 0 ? "overlaps" : "source")
|
| 2973 |
+
|
| 2974 |
// Per-(eval, metric) leaderboards sourced from comparison-index.json.
|
| 2975 |
const benchmarkHistograms = useMemo<Map<string, BenchmarkHistogram>>(() => {
|
| 2976 |
const result = new Map<string, BenchmarkHistogram>()
|
|
|
|
| 5140 |
</div>
|
| 5141 |
</div>
|
| 5142 |
|
| 5143 |
+
{isResearchView && !embedReportedMetricsOnly && (
|
| 5144 |
+
<p className="text-[14px] leading-[1.7] text-[color:var(--fg-muted)] max-w-[64rem] mb-6">
|
| 5145 |
+
{groupingMode === "overlaps"
|
| 5146 |
+
? "Cross-suite overlaps β benchmarks this model reports under more than one suite, with the mean and 95% CI across appearances. Each source links through to its eval. Switch to Source or Category for the full result set."
|
| 5147 |
+
: groupingMode === "category"
|
| 5148 |
+
? "Every reported result, regrouped under curated category tags so similar benchmarks cluster across families."
|
| 5149 |
+
: "Every reported result in the warehouse's natural shape β family-rooted plots and accordions, with no cross-family collapse."}
|
| 5150 |
+
</p>
|
| 5151 |
+
)}
|
| 5152 |
+
|
| 5153 |
{/* SUMMARY VIEW β text-only list grouped by family, ranked
|
| 5154 |
bestβworst, with the category pill bar so non-technical
|
| 5155 |
readers can filter without diving into Researcher view. */}
|
|
|
|
| 5374 |
</div>
|
| 5375 |
<div className="flex flex-wrap gap-1">
|
| 5376 |
{row.appearances.map((app) => (
|
| 5377 |
+
<Link
|
| 5378 |
key={`${row.canonicalKey}::${app.familyKey}::${app.evalSummaryId}`}
|
| 5379 |
+
href={`/evals/${routeIdToPath(app.evalSummaryId)}?from=${encodeURIComponent(currentDetailHref)}`}
|
| 5380 |
+
className="ec-tag outline hover:border-[color:var(--accent)] hover:text-[color:var(--accent)] transition-colors"
|
| 5381 |
style={{ fontSize: 10 }}
|
| 5382 |
+
title={`${app.familyName} Β· ${app.metricName} β view eval`}
|
| 5383 |
>
|
| 5384 |
{app.familyName} Β· {fmt(app.score)}
|
| 5385 |
+
</Link>
|
| 5386 |
))}
|
| 5387 |
</div>
|
| 5388 |
</div>
|
components/family-table.tsx
CHANGED
|
@@ -29,6 +29,12 @@ interface FamilyTableProps {
|
|
| 29 |
* mode β the set is the verified-eval id universe). Null/undefined =
|
| 30 |
* no restriction. */
|
| 31 |
verifiedEvalIds?: Set<string> | null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
sortCol?: FamilySortCol
|
| 33 |
sortDir?: "asc" | "desc"
|
| 34 |
onSort?: (col: FamilySortCol) => void
|
|
@@ -214,6 +220,7 @@ export function FamilyTable({
|
|
| 214 |
categoryFilter,
|
| 215 |
searchQuery,
|
| 216 |
verifiedEvalIds,
|
|
|
|
| 217 |
sortCol,
|
| 218 |
sortDir,
|
| 219 |
onSort,
|
|
@@ -224,9 +231,10 @@ export function FamilyTable({
|
|
| 224 |
const domainFilterActive = Boolean(domainFilter && domainFilter.size > 0)
|
| 225 |
const categoryFilterActive = Boolean(categoryFilter && categoryFilter.size > 0)
|
| 226 |
const verifiedFilterActive = Boolean(verifiedEvalIds)
|
|
|
|
| 227 |
const normalizedQuery = (searchQuery ?? "").trim().toLowerCase()
|
| 228 |
const searchActive = normalizedQuery.length > 0
|
| 229 |
-
const filterActive = domainFilterActive || categoryFilterActive || searchActive || verifiedFilterActive
|
| 230 |
|
| 231 |
function leafMatchesDomain(leaf: LeafEntry): boolean {
|
| 232 |
if (!domainFilterActive || !domainFilter) return true
|
|
@@ -252,10 +260,16 @@ export function FamilyTable({
|
|
| 252 |
return leaf.evalIds.some((id) => verifiedEvalIds.has(id))
|
| 253 |
}
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
function leafMatchesFilter(leaf: LeafEntry, opts?: { skipQuery?: boolean }): boolean {
|
| 256 |
if (!leafMatchesDomain(leaf)) return false
|
| 257 |
if (!leafMatchesCategory(leaf)) return false
|
| 258 |
if (!leafMatchesVerified(leaf)) return false
|
|
|
|
| 259 |
if (!opts?.skipQuery && !leafMatchesQuery(leaf)) return false
|
| 260 |
return true
|
| 261 |
}
|
|
@@ -279,12 +293,12 @@ export function FamilyTable({
|
|
| 279 |
if (leafEntries.some((leaf) => leafMatchesFilter(leaf))) return true
|
| 280 |
// Family-level search match keeps the row even if no leaf survives
|
| 281 |
// the leaf-query filter (the row will fall back to showing all
|
| 282 |
-
// leaves). But the verified
|
| 283 |
-
// resurrect a family that has zero
|
| 284 |
if (
|
| 285 |
searchActive &&
|
| 286 |
familyMatchedAtFamilyLevel(fam) &&
|
| 287 |
-
leafEntries.some((leaf) => leafMatchesVerified(leaf))
|
| 288 |
)
|
| 289 |
return true
|
| 290 |
if (categoryFilterActive && categoryFilter) {
|
|
@@ -402,7 +416,7 @@ export function FamilyTable({
|
|
| 402 |
}
|
| 403 |
return out
|
| 404 |
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 405 |
-
}, [families, evalItems, benchmarkCards, domainFilter, categoryFilter, searchQuery])
|
| 406 |
|
| 407 |
function SortIcon({ col }: { col: FamilySortCol }) {
|
| 408 |
if (!onSort) return null
|
|
|
|
| 29 |
* mode β the set is the verified-eval id universe). Null/undefined =
|
| 30 |
* no restriction. */
|
| 31 |
verifiedEvalIds?: Set<string> | null
|
| 32 |
+
/** When provided, restrict leaves to those mapping to one of these
|
| 33 |
+
* evaluation_ids (drives the /evaluators/<slug> detail page β the set is
|
| 34 |
+
* the eval-id universe owned by one reporting org). Composes with
|
| 35 |
+
* verifiedEvalIds: when both are present a leaf must intersect both.
|
| 36 |
+
* Null/undefined = no restriction. */
|
| 37 |
+
restrictEvalIds?: Set<string> | null
|
| 38 |
sortCol?: FamilySortCol
|
| 39 |
sortDir?: "asc" | "desc"
|
| 40 |
onSort?: (col: FamilySortCol) => void
|
|
|
|
| 220 |
categoryFilter,
|
| 221 |
searchQuery,
|
| 222 |
verifiedEvalIds,
|
| 223 |
+
restrictEvalIds,
|
| 224 |
sortCol,
|
| 225 |
sortDir,
|
| 226 |
onSort,
|
|
|
|
| 231 |
const domainFilterActive = Boolean(domainFilter && domainFilter.size > 0)
|
| 232 |
const categoryFilterActive = Boolean(categoryFilter && categoryFilter.size > 0)
|
| 233 |
const verifiedFilterActive = Boolean(verifiedEvalIds)
|
| 234 |
+
const restrictFilterActive = Boolean(restrictEvalIds)
|
| 235 |
const normalizedQuery = (searchQuery ?? "").trim().toLowerCase()
|
| 236 |
const searchActive = normalizedQuery.length > 0
|
| 237 |
+
const filterActive = domainFilterActive || categoryFilterActive || searchActive || verifiedFilterActive || restrictFilterActive
|
| 238 |
|
| 239 |
function leafMatchesDomain(leaf: LeafEntry): boolean {
|
| 240 |
if (!domainFilterActive || !domainFilter) return true
|
|
|
|
| 260 |
return leaf.evalIds.some((id) => verifiedEvalIds.has(id))
|
| 261 |
}
|
| 262 |
|
| 263 |
+
function leafMatchesRestrict(leaf: LeafEntry): boolean {
|
| 264 |
+
if (!restrictFilterActive || !restrictEvalIds) return true
|
| 265 |
+
return leaf.evalIds.some((id) => restrictEvalIds.has(id))
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
function leafMatchesFilter(leaf: LeafEntry, opts?: { skipQuery?: boolean }): boolean {
|
| 269 |
if (!leafMatchesDomain(leaf)) return false
|
| 270 |
if (!leafMatchesCategory(leaf)) return false
|
| 271 |
if (!leafMatchesVerified(leaf)) return false
|
| 272 |
+
if (!leafMatchesRestrict(leaf)) return false
|
| 273 |
if (!opts?.skipQuery && !leafMatchesQuery(leaf)) return false
|
| 274 |
return true
|
| 275 |
}
|
|
|
|
| 293 |
if (leafEntries.some((leaf) => leafMatchesFilter(leaf))) return true
|
| 294 |
// Family-level search match keeps the row even if no leaf survives
|
| 295 |
// the leaf-query filter (the row will fall back to showing all
|
| 296 |
+
// leaves). But the verified and restrict filters are hard leaf-level
|
| 297 |
+
// gates: never resurrect a family that has zero surviving leaves.
|
| 298 |
if (
|
| 299 |
searchActive &&
|
| 300 |
familyMatchedAtFamilyLevel(fam) &&
|
| 301 |
+
leafEntries.some((leaf) => leafMatchesVerified(leaf) && leafMatchesRestrict(leaf))
|
| 302 |
)
|
| 303 |
return true
|
| 304 |
if (categoryFilterActive && categoryFilter) {
|
|
|
|
| 416 |
}
|
| 417 |
return out
|
| 418 |
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 419 |
+
}, [families, evalItems, benchmarkCards, domainFilter, categoryFilter, searchQuery, verifiedEvalIds, restrictEvalIds])
|
| 420 |
|
| 421 |
function SortIcon({ col }: { col: FamilySortCol }) {
|
| 422 |
if (!onSort) return null
|
lib/policy-summaries.ts
CHANGED
|
@@ -127,6 +127,11 @@ interface ModelPolicyInputs {
|
|
| 127 |
/** Pre-computed third-party tally from caller (cheap to compute, but
|
| 128 |
* caller already has it in benchmark-detail). */
|
| 129 |
thirdPartyEvaluations: number
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
organizationCount: number
|
| 131 |
organizationNames: string[]
|
| 132 |
/** Distinct benchmark count derived from group reduction. */
|
|
@@ -138,12 +143,17 @@ interface ModelPolicyInputs {
|
|
| 138 |
export function buildModelPolicySummary({
|
| 139 |
summary,
|
| 140 |
thirdPartyEvaluations,
|
|
|
|
| 141 |
organizationCount,
|
| 142 |
organizationNames,
|
| 143 |
benchmarkCount,
|
| 144 |
reportedCategories,
|
| 145 |
}: ModelPolicyInputs): ModelPolicySummary {
|
| 146 |
const totalEvals = summary.total_evaluations
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
const repro = summary.reproducibility_summary
|
| 148 |
const reproGap = repro?.has_reproducibility_gap_count ?? 0
|
| 149 |
const reproTotal = repro?.results_total ?? totalEvals
|
|
@@ -191,8 +201,8 @@ export function buildModelPolicySummary({
|
|
| 191 |
provenance?.first_party_only_groups != null && provenance.total_groups > 0
|
| 192 |
? provenance.first_party_only_groups === provenance.total_groups
|
| 193 |
: null
|
| 194 |
-
const allThirdParty =
|
| 195 |
-
const noThirdParty = thirdPartyEvaluations === 0 &&
|
| 196 |
const lead = organizationNames[0]
|
| 197 |
|
| 198 |
let reportingSentence: string
|
|
@@ -253,11 +263,11 @@ export function buildModelPolicySummary({
|
|
| 253 |
|
| 254 |
// ββ 6. Verification headline βββββββββββββββββββββββββββββββββββββββββ
|
| 255 |
let verificationLabel: string | null = null
|
| 256 |
-
if (allThirdParty &&
|
| 257 |
-
verificationLabel = "
|
| 258 |
-
} else if (thirdPartyEvaluations > 0 &&
|
| 259 |
-
const pct = Math.round((thirdPartyEvaluations /
|
| 260 |
-
verificationLabel = `${pct}%
|
| 261 |
} else if (noThirdParty) {
|
| 262 |
verificationLabel = "Developer-reported only"
|
| 263 |
}
|
|
|
|
| 127 |
/** Pre-computed third-party tally from caller (cheap to compute, but
|
| 128 |
* caller already has it in benchmark-detail). */
|
| 129 |
thirdPartyEvaluations: number
|
| 130 |
+
/** Denominator for the third-party share. MUST be counted from the same
|
| 131 |
+
* population as `thirdPartyEvaluations` (the caller's flattened evaluation
|
| 132 |
+
* list), not the warehouse's distinct `total_evaluations` β those have
|
| 133 |
+
* different grains, which made the share exceed 100%. */
|
| 134 |
+
reportedEvaluationCount: number
|
| 135 |
organizationCount: number
|
| 136 |
organizationNames: string[]
|
| 137 |
/** Distinct benchmark count derived from group reduction. */
|
|
|
|
| 143 |
export function buildModelPolicySummary({
|
| 144 |
summary,
|
| 145 |
thirdPartyEvaluations,
|
| 146 |
+
reportedEvaluationCount,
|
| 147 |
organizationCount,
|
| 148 |
organizationNames,
|
| 149 |
benchmarkCount,
|
| 150 |
reportedCategories,
|
| 151 |
}: ModelPolicyInputs): ModelPolicySummary {
|
| 152 |
const totalEvals = summary.total_evaluations
|
| 153 |
+
// Denominator for the third-party share, counted from the same population as
|
| 154 |
+
// the numerator so the ratio stays within 0β100%. Falls back to totalEvals
|
| 155 |
+
// only if the caller passed nothing.
|
| 156 |
+
const thirdPartyBase = reportedEvaluationCount > 0 ? reportedEvaluationCount : totalEvals
|
| 157 |
const repro = summary.reproducibility_summary
|
| 158 |
const reproGap = repro?.has_reproducibility_gap_count ?? 0
|
| 159 |
const reproTotal = repro?.results_total ?? totalEvals
|
|
|
|
| 201 |
provenance?.first_party_only_groups != null && provenance.total_groups > 0
|
| 202 |
? provenance.first_party_only_groups === provenance.total_groups
|
| 203 |
: null
|
| 204 |
+
const allThirdParty = thirdPartyBase > 0 && thirdPartyEvaluations === thirdPartyBase
|
| 205 |
+
const noThirdParty = thirdPartyEvaluations === 0 && thirdPartyBase > 0
|
| 206 |
const lead = organizationNames[0]
|
| 207 |
|
| 208 |
let reportingSentence: string
|
|
|
|
| 263 |
|
| 264 |
// ββ 6. Verification headline βββββββββββββββββββββββββββββββββββββββββ
|
| 265 |
let verificationLabel: string | null = null
|
| 266 |
+
if (allThirdParty && thirdPartyBase > 0) {
|
| 267 |
+
verificationLabel = "100% third party"
|
| 268 |
+
} else if (thirdPartyEvaluations > 0 && thirdPartyBase > 0) {
|
| 269 |
+
const pct = Math.min(100, Math.round((thirdPartyEvaluations / thirdPartyBase) * 100))
|
| 270 |
+
verificationLabel = `${pct}% third party`
|
| 271 |
} else if (noThirdParty) {
|
| 272 |
verificationLabel = "Developer-reported only"
|
| 273 |
}
|