Spaces:
Running
Running
Add rule-based policy-mode summaries for model & eval views
Browse filesGenerates plain-language summaries from the existing signal data via
templating, so policy mode never needs runtime LLM inference. Model
pages get a new <ModelPolicyOverview> with explicit category coverage
and gap callouts; reproducibility and comparability panels swap their
field-level detail for narrative caveats in policy mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
components/benchmark-detail.tsx
CHANGED
|
@@ -54,6 +54,8 @@ import type {
|
|
| 54 |
SubmissionAxis,
|
| 55 |
} from "@/lib/backend-artifacts"
|
| 56 |
import { fetchPeerRanks } from "@/lib/dashboard-data-client"
|
|
|
|
|
|
|
| 57 |
import {
|
| 58 |
buildHierarchyEvalIndex,
|
| 59 |
type HierarchyEvalLocation,
|
|
@@ -2218,6 +2220,40 @@ export function BenchmarkDetail({
|
|
| 2218 |
summary.model_info.name,
|
| 2219 |
])
|
| 2220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2221 |
const benchmarkGroups = useMemo(
|
| 2222 |
() => buildBenchmarkGroups(allCategoryResults, benchmarkCards, currentDetailHref),
|
| 2223 |
[allCategoryResults, benchmarkCards, currentDetailHref]
|
|
@@ -4629,22 +4665,12 @@ export function BenchmarkDetail({
|
|
| 4629 |
)}
|
| 4630 |
</div>
|
| 4631 |
) : (
|
| 4632 |
-
<div className="
|
| 4633 |
-
<
|
| 4634 |
-
{
|
| 4635 |
-
|
| 4636 |
-
|
| 4637 |
-
|
| 4638 |
-
<span className="kicker kicker-accent mr-2">Reproducibility gap</span>
|
| 4639 |
-
<span className="text-[13px] leading-[1.6] text-[color:var(--fg)]">
|
| 4640 |
-
{policySummary.reproducibilityCopy}
|
| 4641 |
-
</span>
|
| 4642 |
-
</div>
|
| 4643 |
-
)}
|
| 4644 |
-
<p className="text-[13px] leading-[1.7] text-[color:var(--fg-muted)]">
|
| 4645 |
-
{policySummary.comparabilityCopy}
|
| 4646 |
-
{policySummary.sizeCaveat ? ` ${policySummary.sizeCaveat}` : ""}
|
| 4647 |
-
</p>
|
| 4648 |
</div>
|
| 4649 |
)}
|
| 4650 |
</section>
|
|
|
|
| 54 |
SubmissionAxis,
|
| 55 |
} from "@/lib/backend-artifacts"
|
| 56 |
import { fetchPeerRanks } from "@/lib/dashboard-data-client"
|
| 57 |
+
import { ModelPolicyOverview } from "@/components/model-policy-overview"
|
| 58 |
+
import { buildModelPolicySummary } from "@/lib/policy-summaries"
|
| 59 |
import {
|
| 60 |
buildHierarchyEvalIndex,
|
| 61 |
type HierarchyEvalLocation,
|
|
|
|
| 2220 |
summary.model_info.name,
|
| 2221 |
])
|
| 2222 |
|
| 2223 |
+
/**
|
| 2224 |
+
* Structured plain-language summary used by <ModelPolicyOverview>.
|
| 2225 |
+
* Pure rule-based templating β see lib/policy-summaries.ts. No LLM is
|
| 2226 |
+
* invoked at runtime, so the same input always produces the same prose.
|
| 2227 |
+
*/
|
| 2228 |
+
const modelPolicySummary = useMemo(() => {
|
| 2229 |
+
const reportedCategories = Array.from(
|
| 2230 |
+
new Set(allCategoryResults.map((entry) => entry.category as unknown as string)),
|
| 2231 |
+
)
|
| 2232 |
+
const benchmarkCount = new Set(
|
| 2233 |
+
allCategoryResults.map(
|
| 2234 |
+
(entry) =>
|
| 2235 |
+
entry.evaluation.benchmark ||
|
| 2236 |
+
entry.evaluation.benchmark_parent_name ||
|
| 2237 |
+
entry.evaluation.eval_summary_id ||
|
| 2238 |
+
getResultBenchmarkName(entry.evaluation, entry.result),
|
| 2239 |
+
),
|
| 2240 |
+
).size
|
| 2241 |
+
return buildModelPolicySummary({
|
| 2242 |
+
summary,
|
| 2243 |
+
thirdPartyEvaluations: reportingStats.thirdPartyEvaluations,
|
| 2244 |
+
organizationCount: reportingStats.organizationCount,
|
| 2245 |
+
organizationNames: reportingStats.organizationNames,
|
| 2246 |
+
benchmarkCount,
|
| 2247 |
+
reportedCategories,
|
| 2248 |
+
})
|
| 2249 |
+
}, [
|
| 2250 |
+
allCategoryResults,
|
| 2251 |
+
reportingStats.thirdPartyEvaluations,
|
| 2252 |
+
reportingStats.organizationCount,
|
| 2253 |
+
reportingStats.organizationNames,
|
| 2254 |
+
summary,
|
| 2255 |
+
])
|
| 2256 |
+
|
| 2257 |
const benchmarkGroups = useMemo(
|
| 2258 |
() => buildBenchmarkGroups(allCategoryResults, benchmarkCards, currentDetailHref),
|
| 2259 |
[allCategoryResults, benchmarkCards, currentDetailHref]
|
|
|
|
| 4665 |
)}
|
| 4666 |
</div>
|
| 4667 |
) : (
|
| 4668 |
+
<div className="max-w-[64rem]">
|
| 4669 |
+
<ModelPolicyOverview
|
| 4670 |
+
modelName={getModelDisplayName(summary.model_info.name)}
|
| 4671 |
+
policySummary={modelPolicySummary}
|
| 4672 |
+
scaleNote={policySummary.sizeCaveat}
|
| 4673 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4674 |
</div>
|
| 4675 |
)}
|
| 4676 |
</section>
|
components/model-policy-overview.tsx
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { BookOpen, ShieldCheck, AlertTriangle, GitCompareArrows, Users, Layers } from "lucide-react"
|
| 4 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 5 |
+
import type { ModelPolicySummary } from "@/lib/policy-summaries"
|
| 6 |
+
|
| 7 |
+
interface ModelPolicyOverviewProps {
|
| 8 |
+
/** Display name of the model β surfaced in the kicker. */
|
| 9 |
+
modelName: string
|
| 10 |
+
policySummary: ModelPolicySummary
|
| 11 |
+
/** Optional one-line model-scale framing (params / size context). */
|
| 12 |
+
scaleNote?: string | null
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Plain-language policy note for the model detail page. Mirrors the structure
|
| 17 |
+
* of `<PolicyOverview>` for the eval page but reframes around model-level
|
| 18 |
+
* questions: how broad is the evidence, who reported it, can it be re-run,
|
| 19 |
+
* can it be compared.
|
| 20 |
+
*
|
| 21 |
+
* All copy is rule-based β see lib/policy-summaries.ts. There is no live
|
| 22 |
+
* LLM inference at runtime.
|
| 23 |
+
*/
|
| 24 |
+
export function ModelPolicyOverview({ modelName, policySummary, scaleNote }: ModelPolicyOverviewProps) {
|
| 25 |
+
const {
|
| 26 |
+
scopeSentence,
|
| 27 |
+
coverageSentence,
|
| 28 |
+
gapSentence,
|
| 29 |
+
reportingSentence,
|
| 30 |
+
reproducibilitySentence,
|
| 31 |
+
comparabilitySentence,
|
| 32 |
+
verificationLabel,
|
| 33 |
+
} = policySummary
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<section className="ec-card warm" style={{ padding: "20px 24px" }}>
|
| 37 |
+
<header className="mb-3 flex flex-wrap items-center gap-3">
|
| 38 |
+
<BookOpen className="h-4 w-4" style={{ color: "var(--fg-muted)" }} />
|
| 39 |
+
<span className="kicker kicker-fg" style={{ fontSize: 12, letterSpacing: "0.16em" }}>
|
| 40 |
+
Policy note
|
| 41 |
+
</span>
|
| 42 |
+
<span
|
| 43 |
+
className="font-mono text-[10px] uppercase tracking-[0.12em]"
|
| 44 |
+
style={{ color: "var(--fg-subtle)" }}
|
| 45 |
+
>
|
| 46 |
+
{modelName} Β· in plain language
|
| 47 |
+
</span>
|
| 48 |
+
{verificationLabel && (
|
| 49 |
+
<span
|
| 50 |
+
className="ec-tag outline ml-auto"
|
| 51 |
+
style={{ textTransform: "uppercase" }}
|
| 52 |
+
title="Whether scores have been reported by parties other than the model's developer."
|
| 53 |
+
>
|
| 54 |
+
<ShieldCheck className="h-3 w-3 shrink-0" />
|
| 55 |
+
{verificationLabel}
|
| 56 |
+
</span>
|
| 57 |
+
)}
|
| 58 |
+
</header>
|
| 59 |
+
|
| 60 |
+
<dl
|
| 61 |
+
className="grid gap-y-3 text-[14px]"
|
| 62 |
+
style={{ gridTemplateColumns: "max-content 1fr", columnGap: 24 }}
|
| 63 |
+
>
|
| 64 |
+
<Row icon="layers" label="Reported on">
|
| 65 |
+
{scopeSentence}
|
| 66 |
+
{coverageSentence ? <> {coverageSentence}</> : null}
|
| 67 |
+
</Row>
|
| 68 |
+
|
| 69 |
+
{gapSentence && (
|
| 70 |
+
<Row icon="alert" label="Gap" tone="accent">
|
| 71 |
+
{gapSentence}
|
| 72 |
+
</Row>
|
| 73 |
+
)}
|
| 74 |
+
|
| 75 |
+
<Row icon="users" label="Reported by">
|
| 76 |
+
{reportingSentence}
|
| 77 |
+
</Row>
|
| 78 |
+
|
| 79 |
+
{reproducibilitySentence && (
|
| 80 |
+
<Row icon="alert" label="Re-runnable">
|
| 81 |
+
<SignalTooltip content="Whether someone could re-run this evaluation with the information available.">
|
| 82 |
+
<span
|
| 83 |
+
className="underline decoration-dotted underline-offset-4 cursor-help"
|
| 84 |
+
style={{ textDecorationColor: "var(--fg-subtle)" }}
|
| 85 |
+
>
|
| 86 |
+
{reproducibilitySentence}
|
| 87 |
+
</span>
|
| 88 |
+
</SignalTooltip>
|
| 89 |
+
</Row>
|
| 90 |
+
)}
|
| 91 |
+
|
| 92 |
+
{comparabilitySentence && (
|
| 93 |
+
<Row icon="compare" label="Comparable">
|
| 94 |
+
<SignalTooltip content="Flags when score differences may come from setup choices or different reporting sources.">
|
| 95 |
+
<span
|
| 96 |
+
className="underline decoration-dotted underline-offset-4 cursor-help"
|
| 97 |
+
style={{ textDecorationColor: "var(--fg-subtle)" }}
|
| 98 |
+
>
|
| 99 |
+
{comparabilitySentence}
|
| 100 |
+
</span>
|
| 101 |
+
</SignalTooltip>
|
| 102 |
+
</Row>
|
| 103 |
+
)}
|
| 104 |
+
|
| 105 |
+
{scaleNote && (
|
| 106 |
+
<Row label="Scale note">
|
| 107 |
+
{scaleNote}
|
| 108 |
+
</Row>
|
| 109 |
+
)}
|
| 110 |
+
</dl>
|
| 111 |
+
</section>
|
| 112 |
+
)
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
function Row({
|
| 116 |
+
icon,
|
| 117 |
+
label,
|
| 118 |
+
tone,
|
| 119 |
+
children,
|
| 120 |
+
}: {
|
| 121 |
+
icon?: "layers" | "alert" | "compare" | "users"
|
| 122 |
+
label: string
|
| 123 |
+
tone?: "accent"
|
| 124 |
+
children: React.ReactNode
|
| 125 |
+
}) {
|
| 126 |
+
const Icon =
|
| 127 |
+
icon === "layers"
|
| 128 |
+
? Layers
|
| 129 |
+
: icon === "alert"
|
| 130 |
+
? AlertTriangle
|
| 131 |
+
: icon === "compare"
|
| 132 |
+
? GitCompareArrows
|
| 133 |
+
: icon === "users"
|
| 134 |
+
? Users
|
| 135 |
+
: null
|
| 136 |
+
|
| 137 |
+
return (
|
| 138 |
+
<>
|
| 139 |
+
<dt
|
| 140 |
+
className="font-mono uppercase tracking-[0.14em] inline-flex items-center gap-1.5"
|
| 141 |
+
style={{
|
| 142 |
+
fontSize: 10,
|
| 143 |
+
color: tone === "accent" ? "var(--accent)" : "var(--fg-subtle)",
|
| 144 |
+
paddingTop: 3,
|
| 145 |
+
}}
|
| 146 |
+
>
|
| 147 |
+
{Icon && <Icon className="h-3 w-3 shrink-0" />}
|
| 148 |
+
{label}
|
| 149 |
+
</dt>
|
| 150 |
+
<dd style={{ color: "var(--fg)", lineHeight: 1.6, margin: 0 }}>
|
| 151 |
+
{children}
|
| 152 |
+
</dd>
|
| 153 |
+
</>
|
| 154 |
+
)
|
| 155 |
+
}
|
components/signals/comparability-panel.tsx
CHANGED
|
@@ -60,11 +60,19 @@ export function ComparabilityPanel({
|
|
| 60 |
|
| 61 |
{showNoCrossPartyNote && (
|
| 62 |
<div className="mt-4 rounded-xl border border-dashed border-border/70 bg-muted/10 px-3 py-2 text-sm text-muted-foreground">
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
)}
|
| 66 |
|
| 67 |
-
{(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
const onlyOne =
|
| 69 |
(variantGroups.length > 0 ? 1 : 0) + (crossPartyGroups.length > 0 ? 1 : 0) === 1
|
| 70 |
const sectionClass = onlyOne ? "" : "lg:grid lg:grid-cols-2 lg:gap-3"
|
|
@@ -116,6 +124,36 @@ export function ComparabilityPanel({
|
|
| 116 |
)
|
| 117 |
}
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
function GroupList({
|
| 120 |
icon,
|
| 121 |
title,
|
|
|
|
| 60 |
|
| 61 |
{showNoCrossPartyNote && (
|
| 62 |
<div className="mt-4 rounded-xl border border-dashed border-border/70 bg-muted/10 px-3 py-2 text-sm text-muted-foreground">
|
| 63 |
+
{isResearchView
|
| 64 |
+
? "No third-party reports are available for cross-party comparison."
|
| 65 |
+
: "No independent third-party reports are available to cross-check the developer's numbers on this benchmark."}
|
| 66 |
</div>
|
| 67 |
)}
|
| 68 |
|
| 69 |
+
{!isResearchView && (variantGroups.length > 0 || crossPartyGroups.length > 0) && (
|
| 70 |
+
<div className="mt-4 rounded-xl border border-border/70 bg-muted/5 px-4 py-3 text-sm leading-relaxed text-foreground/90">
|
| 71 |
+
{buildPolicyComparabilitySentence(variantGroups.length, crossPartyGroups.length)}
|
| 72 |
+
</div>
|
| 73 |
+
)}
|
| 74 |
+
|
| 75 |
+
{isResearchView && (() => {
|
| 76 |
const onlyOne =
|
| 77 |
(variantGroups.length > 0 ? 1 : 0) + (crossPartyGroups.length > 0 ? 1 : 0) === 1
|
| 78 |
const sectionClass = onlyOne ? "" : "lg:grid lg:grid-cols-2 lg:gap-3"
|
|
|
|
| 124 |
)
|
| 125 |
}
|
| 126 |
|
| 127 |
+
/**
|
| 128 |
+
* Policy-mode caveat. Hides field names and divergence magnitudes (per the
|
| 129 |
+
* policy spec) and rolls the counts into a single narrative line.
|
| 130 |
+
*/
|
| 131 |
+
function buildPolicyComparabilitySentence(variantCount: number, crossPartyCount: number): string {
|
| 132 |
+
const variantPhrase =
|
| 133 |
+
variantCount === 0
|
| 134 |
+
? null
|
| 135 |
+
: variantCount === 1
|
| 136 |
+
? "one model has been reported under different evaluation setups"
|
| 137 |
+
: `${variantCount} models have been reported under different evaluation setups`
|
| 138 |
+
const crossPartyPhrase =
|
| 139 |
+
crossPartyCount === 0
|
| 140 |
+
? null
|
| 141 |
+
: crossPartyCount === 1
|
| 142 |
+
? "one model has different scores reported by different organizations"
|
| 143 |
+
: `${crossPartyCount} models have different scores reported by different organizations`
|
| 144 |
+
|
| 145 |
+
if (variantPhrase && crossPartyPhrase) {
|
| 146 |
+
return `${variantPhrase[0].toUpperCase()}${variantPhrase.slice(1)}, and ${crossPartyPhrase}. Some apparent score differences may reflect those choices rather than capability.`
|
| 147 |
+
}
|
| 148 |
+
if (variantPhrase) {
|
| 149 |
+
return `${variantPhrase[0].toUpperCase()}${variantPhrase.slice(1)}, which may explain some of the variation seen in reported numbers.`
|
| 150 |
+
}
|
| 151 |
+
if (crossPartyPhrase) {
|
| 152 |
+
return `${crossPartyPhrase[0].toUpperCase()}${crossPartyPhrase.slice(1)} β treat the headline number as a range rather than a single value.`
|
| 153 |
+
}
|
| 154 |
+
return ""
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
function GroupList({
|
| 158 |
icon,
|
| 159 |
title,
|
components/signals/reproducibility-panel.tsx
CHANGED
|
@@ -6,6 +6,26 @@ import { useAudienceMode } from "@/components/audience-mode-provider"
|
|
| 6 |
import type { ReproducibilityGap } from "@/lib/backend-artifacts"
|
| 7 |
import { formatMissingField } from "./signal-utils"
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
export function ReproducibilityPanel({
|
| 10 |
gap,
|
| 11 |
}: {
|
|
@@ -34,18 +54,24 @@ export function ReproducibilityPanel({
|
|
| 34 |
</div>
|
| 35 |
</div>
|
| 36 |
|
| 37 |
-
|
| 38 |
-
<
|
| 39 |
-
label="Setup fields recorded"
|
| 40 |
-
value={`${gap.populated_field_count} of ${gap.required_field_count}`}
|
| 41 |
-
/>
|
| 42 |
-
{gap.missing_fields.length > 0 && (
|
| 43 |
<PanelRow
|
| 44 |
-
label="
|
| 45 |
-
value={gap.
|
| 46 |
/>
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
</div>
|
| 50 |
)
|
| 51 |
}
|
|
|
|
| 6 |
import type { ReproducibilityGap } from "@/lib/backend-artifacts"
|
| 7 |
import { formatMissingField } from "./signal-utils"
|
| 8 |
|
| 9 |
+
/**
|
| 10 |
+
* Policy mode hides field-level detail (per the policy spec) and renders a
|
| 11 |
+
* single plain-language sentence built from the same gap counts that drive
|
| 12 |
+
* the research-mode rows. Pure rule-based templating β no LLM at runtime.
|
| 13 |
+
*/
|
| 14 |
+
function buildPolicyReproducibilitySentence(gap: ReproducibilityGap): string {
|
| 15 |
+
const total = gap.required_field_count
|
| 16 |
+
const populated = gap.populated_field_count
|
| 17 |
+
if (total === 0) {
|
| 18 |
+
return "Setup documentation is not applicable for this result."
|
| 19 |
+
}
|
| 20 |
+
if (populated === total) {
|
| 21 |
+
return "How this model was prompted during testing is fully documented for this result."
|
| 22 |
+
}
|
| 23 |
+
if (populated === 0) {
|
| 24 |
+
return "How this model was prompted during testing is not documented. This score cannot be independently re-run as reported."
|
| 25 |
+
}
|
| 26 |
+
return `${populated} of ${total} setup fields are recorded; the rest are missing, which means the score cannot be re-run exactly as reported.`
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
export function ReproducibilityPanel({
|
| 30 |
gap,
|
| 31 |
}: {
|
|
|
|
| 54 |
</div>
|
| 55 |
</div>
|
| 56 |
|
| 57 |
+
{isResearchView ? (
|
| 58 |
+
<div className="space-y-2.5 text-sm">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
<PanelRow
|
| 60 |
+
label="Setup fields recorded"
|
| 61 |
+
value={`${gap.populated_field_count} of ${gap.required_field_count}`}
|
| 62 |
/>
|
| 63 |
+
{gap.missing_fields.length > 0 && (
|
| 64 |
+
<PanelRow
|
| 65 |
+
label="Missing"
|
| 66 |
+
value={gap.missing_fields.map(formatMissingField).join(", ")}
|
| 67 |
+
/>
|
| 68 |
+
)}
|
| 69 |
+
</div>
|
| 70 |
+
) : (
|
| 71 |
+
<p className="text-sm leading-relaxed text-foreground/90">
|
| 72 |
+
{buildPolicyReproducibilitySentence(gap)}
|
| 73 |
+
</p>
|
| 74 |
+
)}
|
| 75 |
</div>
|
| 76 |
)
|
| 77 |
}
|
lib/policy-summaries.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Rule-based plain-language summaries for the policy-mode views.
|
| 3 |
+
*
|
| 4 |
+
* Pure templating β no live LLM calls. Each function takes a structured
|
| 5 |
+
* data object (model summary, eval summary, signal block) and returns
|
| 6 |
+
* either a single sentence or a small struct of paragraph fragments.
|
| 7 |
+
*
|
| 8 |
+
* Templating rules of thumb:
|
| 9 |
+
* - Lead with the headline (numbers / coverage), then the caveat.
|
| 10 |
+
* - Pick exactly one phrasing per branch β readers should never see
|
| 11 |
+
* two stitched-together fragments that mean the same thing.
|
| 12 |
+
* - "Not specified" sentinels collapse silently (caller decides whether
|
| 13 |
+
* to render the row at all).
|
| 14 |
+
*/
|
| 15 |
+
import type { ModelSummaryCore, BenchmarkEvaluation, MetricConfig } from "@/lib/benchmark-schema"
|
| 16 |
+
import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
|
| 17 |
+
import type { ProvenanceSummary, ReproducibilitySummary, ComparabilitySummary } from "@/lib/backend-artifacts"
|
| 18 |
+
import { formatTagLabel } from "@/lib/benchmark-tags"
|
| 19 |
+
|
| 20 |
+
// ---------------------------------------------------------------------------
|
| 21 |
+
// Sentence-list helpers (kept tiny & pure β no JSX, no React)
|
| 22 |
+
// ---------------------------------------------------------------------------
|
| 23 |
+
|
| 24 |
+
/** Oxford-comma list with "and". `["a","b","c"]` β `"a, b, and c"`. */
|
| 25 |
+
export function listAnd(items: readonly string[]): string {
|
| 26 |
+
if (items.length === 0) return ""
|
| 27 |
+
if (items.length === 1) return items[0]
|
| 28 |
+
if (items.length === 2) return `${items[0]} and ${items[1]}`
|
| 29 |
+
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
/** Plural-aware count phrase. `(1, "result")` β `"1 result"`. */
|
| 33 |
+
export function pluralize(count: number, singular: string, plural?: string): string {
|
| 34 |
+
return `${count.toLocaleString()} ${count === 1 ? singular : plural ?? `${singular}s`}`
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* Categories the EvalCards taxonomy can express. We compare against this
|
| 39 |
+
* canonical list to surface what *isn't* reported, not just what is.
|
| 40 |
+
*
|
| 41 |
+
* Sourced from data/benchmarks/categories.json β top-level tags that the
|
| 42 |
+
* derivedTag pipeline produces. We list the headliners only; obscure
|
| 43 |
+
* categories ("multilingual_general", "video_understanding") aren't
|
| 44 |
+
* useful as gap-callouts on a policy summary.
|
| 45 |
+
*/
|
| 46 |
+
export const HEADLINE_POLICY_CATEGORIES = [
|
| 47 |
+
"general",
|
| 48 |
+
"knowledge",
|
| 49 |
+
"logical_reasoning",
|
| 50 |
+
"applied_reasoning",
|
| 51 |
+
"mathematics",
|
| 52 |
+
"coding",
|
| 53 |
+
"agentic",
|
| 54 |
+
"safety",
|
| 55 |
+
"multilingual_general",
|
| 56 |
+
"multimodal",
|
| 57 |
+
] as const
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Map a derivedTag category into a small bucket of "policy-relevant"
|
| 61 |
+
* groupings, so e.g. logical_reasoning + applied_reasoning collapse to
|
| 62 |
+
* "Reasoning" for a non-technical reader. Returns null when the input is
|
| 63 |
+
* neither headlinable nor in the policy bucket map.
|
| 64 |
+
*/
|
| 65 |
+
const POLICY_BUCKETS: Record<string, string> = {
|
| 66 |
+
general: "General capability",
|
| 67 |
+
knowledge: "Knowledge",
|
| 68 |
+
logical_reasoning: "Reasoning",
|
| 69 |
+
applied_reasoning: "Reasoning",
|
| 70 |
+
commonsense_reasoning: "Reasoning",
|
| 71 |
+
mathematics: "Math",
|
| 72 |
+
coding: "Coding",
|
| 73 |
+
software_engineering: "Coding",
|
| 74 |
+
agentic: "Agentic",
|
| 75 |
+
safety: "Safety",
|
| 76 |
+
multilingual_general: "Multilingual",
|
| 77 |
+
multimodal: "Multimodal",
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/** Group categories into ~6 policy-readable buckets. */
|
| 81 |
+
export function bucketCategories(tags: readonly string[]): string[] {
|
| 82 |
+
const seen = new Set<string>()
|
| 83 |
+
const out: string[] = []
|
| 84 |
+
for (const tag of tags) {
|
| 85 |
+
const bucket = POLICY_BUCKETS[tag]
|
| 86 |
+
if (bucket && !seen.has(bucket)) {
|
| 87 |
+
seen.add(bucket)
|
| 88 |
+
out.push(bucket)
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
return out
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const HEADLINE_BUCKET_LIST = ["General capability", "Knowledge", "Reasoning", "Math", "Coding", "Agentic", "Safety"] as const
|
| 95 |
+
|
| 96 |
+
/** Buckets we'd expect a frontier general-purpose model to report on. */
|
| 97 |
+
function expectedBuckets(): readonly string[] {
|
| 98 |
+
return HEADLINE_BUCKET_LIST
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// ---------------------------------------------------------------------------
|
| 102 |
+
// MODEL view β produces the Measures / Caveat / Coverage / Reporting block.
|
| 103 |
+
// ---------------------------------------------------------------------------
|
| 104 |
+
|
| 105 |
+
export interface ModelPolicySummary {
|
| 106 |
+
/** "Reported across N benchmarks in K categories." */
|
| 107 |
+
scopeSentence: string
|
| 108 |
+
/** "Coverage spans Reasoning, Knowledge, and Agentic." (or null when there's only one category) */
|
| 109 |
+
coverageSentence: string | null
|
| 110 |
+
/** "No Safety or Math evaluations have been reported." Returns null when nothing material is missing. */
|
| 111 |
+
gapSentence: string | null
|
| 112 |
+
/** "Reported by Anthropic (the developer) and one independent third party." */
|
| 113 |
+
reportingSentence: string
|
| 114 |
+
/** "How this model was prompted is documented for X of Y reported scores." or null when fully documented / no data. */
|
| 115 |
+
reproducibilitySentence: string | null
|
| 116 |
+
/** "Comparing scores directly is limited because reporting setups differ." or null. */
|
| 117 |
+
comparabilitySentence: string | null
|
| 118 |
+
/** "Independently verified across N benchmarks." used as the optional headline tag. */
|
| 119 |
+
verificationLabel: string | null
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
interface ModelPolicyInputs {
|
| 123 |
+
/** Accepts any ModelSummaryCore-shaped object β the model page passes
|
| 124 |
+
* either the family summary or a selected variant. We only read the
|
| 125 |
+
* signal-summary blocks plus `total_evaluations`. */
|
| 126 |
+
summary: ModelSummaryCore
|
| 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. */
|
| 133 |
+
benchmarkCount: number
|
| 134 |
+
/** Categories actually reported, derived-tag form (snake_case). */
|
| 135 |
+
reportedCategories: readonly string[]
|
| 136 |
+
}
|
| 137 |
+
|
| 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
|
| 150 |
+
const provenance = summary.provenance_summary
|
| 151 |
+
const comparability = summary.comparability_summary
|
| 152 |
+
|
| 153 |
+
// ββ 1. Scope ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 154 |
+
const scopeSentence =
|
| 155 |
+
benchmarkCount === 0
|
| 156 |
+
? "No benchmark evaluations have been reported for this model."
|
| 157 |
+
: `Reported across ${pluralize(benchmarkCount, "benchmark")}` +
|
| 158 |
+
(totalEvals > benchmarkCount
|
| 159 |
+
? ` (${pluralize(totalEvals, "result")} total).`
|
| 160 |
+
: ".")
|
| 161 |
+
|
| 162 |
+
// ββ 2. Coverage / Gap (the "missing categories" piece the user wanted) ββ
|
| 163 |
+
const reportedBuckets = bucketCategories(reportedCategories)
|
| 164 |
+
let coverageSentence: string | null = null
|
| 165 |
+
let gapSentence: string | null = null
|
| 166 |
+
|
| 167 |
+
if (reportedBuckets.length > 1) {
|
| 168 |
+
coverageSentence = `Coverage spans ${listAnd(reportedBuckets)}.`
|
| 169 |
+
} else if (reportedBuckets.length === 1) {
|
| 170 |
+
coverageSentence = `Coverage is concentrated in ${reportedBuckets[0]} only.`
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
if (reportedBuckets.length > 0) {
|
| 174 |
+
const reportedSet = new Set(reportedBuckets)
|
| 175 |
+
const missing = expectedBuckets().filter((b) => !reportedSet.has(b))
|
| 176 |
+
// Only flag a gap when there's a meaningful absence β at least one
|
| 177 |
+
// category reported AND at least one common bucket missing. We cap the
|
| 178 |
+
// list at three to stay readable.
|
| 179 |
+
if (missing.length > 0 && missing.length < expectedBuckets().length) {
|
| 180 |
+
const head = missing.slice(0, 3)
|
| 181 |
+
const trail = missing.length > 3 ? ` (and ${missing.length - 3} other categories)` : ""
|
| 182 |
+
gapSentence =
|
| 183 |
+
head.length === 1
|
| 184 |
+
? `No ${head[0]} evaluations have been reported.`
|
| 185 |
+
: `No ${listAnd(head)} evaluations have been reported${trail}.`
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// ββ 3. Reporting (provenance) βββββββββββββββββββββββββββββββββββββββββββ
|
| 190 |
+
const firstPartyOnly =
|
| 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 = totalEvals > 0 && thirdPartyEvaluations === totalEvals
|
| 195 |
+
const noThirdParty = thirdPartyEvaluations === 0 && totalEvals > 0
|
| 196 |
+
const lead = organizationNames[0]
|
| 197 |
+
|
| 198 |
+
let reportingSentence: string
|
| 199 |
+
if (organizationCount === 0) {
|
| 200 |
+
reportingSentence = "No reporting organization is recorded."
|
| 201 |
+
} else if (organizationCount === 1 && lead) {
|
| 202 |
+
reportingSentence = allThirdParty
|
| 203 |
+
? `Tested independently by ${lead} β a third party, not the model's developer.`
|
| 204 |
+
: noThirdParty
|
| 205 |
+
? `Reported only by ${lead}; no independent third-party scores are available.`
|
| 206 |
+
: `Reported by ${lead}.`
|
| 207 |
+
} else if (lead) {
|
| 208 |
+
const others = organizationCount - 1
|
| 209 |
+
reportingSentence = allThirdParty
|
| 210 |
+
? `Tested independently by ${lead} and ${pluralize(others, "other organization")}.`
|
| 211 |
+
: noThirdParty
|
| 212 |
+
? `Reported by ${lead} and ${pluralize(others, "other organization")} β but no independent third-party scores are available.`
|
| 213 |
+
: `Reported by ${lead} and ${pluralize(others, "other organization")}.`
|
| 214 |
+
} else {
|
| 215 |
+
reportingSentence = `Reported by ${pluralize(organizationCount, "organization")}.`
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// ββ 4. Reproducibility gap (plain language, no field names) ββββββββββββ
|
| 219 |
+
let reproducibilitySentence: string | null = null
|
| 220 |
+
if (reproTotal > 0) {
|
| 221 |
+
if (reproGap === 0) {
|
| 222 |
+
reproducibilitySentence = "How this model was prompted during testing is documented for every reported score."
|
| 223 |
+
} else if (reproGap === reproTotal) {
|
| 224 |
+
reproducibilitySentence =
|
| 225 |
+
"How this model was prompted during testing is not documented. Scores cannot be independently re-run as reported."
|
| 226 |
+
} else {
|
| 227 |
+
const documented = reproTotal - reproGap
|
| 228 |
+
const pct = Math.round((documented / reproTotal) * 100)
|
| 229 |
+
reproducibilitySentence = `Prompting setup is documented for ${pct}% of reported scores (${documented} of ${reproTotal}); the rest are missing enough detail to be re-run as-is.`
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// ββ 5. Comparability caveat (no field names) ββββββββββββββββββββββββββ
|
| 234 |
+
let comparabilitySentence: string | null = null
|
| 235 |
+
if (comparability) {
|
| 236 |
+
const variantHits = comparability.variant_divergent_count
|
| 237 |
+
const crossPartyHits = comparability.cross_party_divergent_count
|
| 238 |
+
const noCrossPartyChecks = comparability.groups_with_cross_party_check === 0
|
| 239 |
+
if (variantHits === 0 && crossPartyHits === 0 && !noCrossPartyChecks) {
|
| 240 |
+
comparabilitySentence = "Where multiple reports are available, scores agree closely across setups and reporters."
|
| 241 |
+
} else if (variantHits > 0 && crossPartyHits > 0) {
|
| 242 |
+
comparabilitySentence = `Scores diverge across reporting setups in ${pluralize(variantHits, "case")} and across different reporters in ${pluralize(crossPartyHits, "case")}; some apparent score gaps may reflect setup choices rather than capability.`
|
| 243 |
+
} else if (variantHits > 0) {
|
| 244 |
+
comparabilitySentence = `Scores diverge across reporting setups in ${pluralize(variantHits, "case")}; apparent score gaps may partly reflect those setup choices.`
|
| 245 |
+
} else if (crossPartyHits > 0) {
|
| 246 |
+
comparabilitySentence = `Different reporters disagree on ${pluralize(crossPartyHits, "score")}; treat headline numbers as a range rather than a single value.`
|
| 247 |
+
} else if (noCrossPartyChecks) {
|
| 248 |
+
comparabilitySentence = "No third-party reports are available to cross-check the developer's numbers."
|
| 249 |
+
}
|
| 250 |
+
} else if (firstPartyOnly === true) {
|
| 251 |
+
comparabilitySentence = "Only the model's developer has reported these scores; cross-party comparison is not possible."
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// ββ 6. Verification headline βββββββββββββββββββββββββββββββββββββββββ
|
| 255 |
+
let verificationLabel: string | null = null
|
| 256 |
+
if (allThirdParty && totalEvals > 0) {
|
| 257 |
+
verificationLabel = "Independently verified"
|
| 258 |
+
} else if (thirdPartyEvaluations > 0 && totalEvals > 0) {
|
| 259 |
+
const pct = Math.round((thirdPartyEvaluations / totalEvals) * 100)
|
| 260 |
+
verificationLabel = `${pct}% independently verified`
|
| 261 |
+
} else if (noThirdParty) {
|
| 262 |
+
verificationLabel = "Developer-reported only"
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
return {
|
| 266 |
+
scopeSentence,
|
| 267 |
+
coverageSentence,
|
| 268 |
+
gapSentence,
|
| 269 |
+
reportingSentence,
|
| 270 |
+
reproducibilitySentence,
|
| 271 |
+
comparabilitySentence,
|
| 272 |
+
verificationLabel,
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
// ---------------------------------------------------------------------------
|
| 277 |
+
// EVAL view β produces single-sentence narrative for each of the four
|
| 278 |
+
// interpretive signals (used by the policy-mode panel renderers).
|
| 279 |
+
// ---------------------------------------------------------------------------
|
| 280 |
+
|
| 281 |
+
export interface EvalPolicySignals {
|
| 282 |
+
metricSentence: string
|
| 283 |
+
reproducibilitySentence: string | null
|
| 284 |
+
provenanceSentence: string | null
|
| 285 |
+
comparabilitySentence: string | null
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
export function buildEvalPolicySignals(summary: BenchmarkEvalSummary): EvalPolicySignals {
|
| 289 |
+
const cfg = summary.metric_config
|
| 290 |
+
const metricSentence = formatMetricSentence(cfg)
|
| 291 |
+
|
| 292 |
+
const reproducibilitySentence = formatReproducibilitySentence(summary.reproducibility_summary)
|
| 293 |
+
const provenanceSentence = formatProvenanceSentence(summary)
|
| 294 |
+
const comparabilitySentence = formatComparabilitySentence(summary.comparability_summary)
|
| 295 |
+
|
| 296 |
+
return {
|
| 297 |
+
metricSentence,
|
| 298 |
+
reproducibilitySentence,
|
| 299 |
+
provenanceSentence,
|
| 300 |
+
comparabilitySentence,
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
function formatMetricSentence(cfg: MetricConfig): string {
|
| 305 |
+
const lower = cfg.lower_is_better
|
| 306 |
+
const min = cfg.min_score
|
| 307 |
+
const max = cfg.max_score
|
| 308 |
+
|
| 309 |
+
const direction = lower
|
| 310 |
+
? "Lower scores indicate better performance"
|
| 311 |
+
: "Higher scores indicate better performance"
|
| 312 |
+
|
| 313 |
+
// Only mention the scale when both ends are documented and look like a
|
| 314 |
+
// tidy interval. Otherwise the sentence collapses to direction-only.
|
| 315 |
+
if (typeof min === "number" && typeof max === "number" && max > min) {
|
| 316 |
+
if (min === 0 && max === 1) return `${direction}, on a 0 to 1 scale.`
|
| 317 |
+
if (min === 0 && max === 100) return `${direction}, on a 0 to 100 scale.`
|
| 318 |
+
return `${direction}, on a ${min} to ${max} scale.`
|
| 319 |
+
}
|
| 320 |
+
return `${direction}.`
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
function formatReproducibilitySentence(repro?: ReproducibilitySummary): string | null {
|
| 324 |
+
if (!repro || repro.results_total === 0) return null
|
| 325 |
+
const total = repro.results_total
|
| 326 |
+
const gap = repro.has_reproducibility_gap_count
|
| 327 |
+
if (gap === 0) {
|
| 328 |
+
return "How models were prompted during testing is documented for every reported score."
|
| 329 |
+
}
|
| 330 |
+
if (gap === total) {
|
| 331 |
+
return "How models were prompted during testing is not documented. Scores cannot be independently re-run as reported."
|
| 332 |
+
}
|
| 333 |
+
const documented = total - gap
|
| 334 |
+
const pct = Math.round((documented / total) * 100)
|
| 335 |
+
return `Prompting setup is documented for ${pct}% of reported scores (${documented} of ${total}).`
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
function formatProvenanceSentence(summary: BenchmarkEvalSummary): string | null {
|
| 339 |
+
const prov: ProvenanceSummary | undefined = summary.provenance_summary
|
| 340 |
+
if (!prov) {
|
| 341 |
+
// Fall back to coarser ratio when the summary isn't attached.
|
| 342 |
+
if (summary.third_party_ratio === 0) {
|
| 343 |
+
return "These scores were reported only by the model developers themselves."
|
| 344 |
+
}
|
| 345 |
+
if (summary.third_party_ratio === 1) {
|
| 346 |
+
return "These scores were reported by independent third parties, not the model developers."
|
| 347 |
+
}
|
| 348 |
+
return null
|
| 349 |
+
}
|
| 350 |
+
const total = prov.total_groups
|
| 351 |
+
if (total === 0) return null
|
| 352 |
+
const firstPartyOnly = prov.first_party_only_groups
|
| 353 |
+
const multi = prov.multi_source_groups
|
| 354 |
+
if (firstPartyOnly === total) {
|
| 355 |
+
return "Every reported score on this benchmark comes only from the model's own developer; no independent third-party numbers are available."
|
| 356 |
+
}
|
| 357 |
+
if (firstPartyOnly === 0 && multi === 0) {
|
| 358 |
+
return "All reported scores come from independent third parties rather than the model developers."
|
| 359 |
+
}
|
| 360 |
+
if (multi > 0) {
|
| 361 |
+
const pctMulti = Math.round((multi / total) * 100)
|
| 362 |
+
return `${pctMulti}% of reported scores have been corroborated by more than one reporting organization.`
|
| 363 |
+
}
|
| 364 |
+
return null
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
function formatComparabilitySentence(comp?: ComparabilitySummary): string | null {
|
| 368 |
+
if (!comp || comp.total_groups === 0) return null
|
| 369 |
+
const variant = comp.variant_divergent_count
|
| 370 |
+
const crossParty = comp.cross_party_divergent_count
|
| 371 |
+
const noCrossPartyChecks = comp.groups_with_cross_party_check === 0
|
| 372 |
+
if (variant === 0 && crossParty === 0 && !noCrossPartyChecks) {
|
| 373 |
+
return "Where multiple reports exist, the scores agree closely β direct comparison across reports is reasonable."
|
| 374 |
+
}
|
| 375 |
+
if (variant > 0 && crossParty > 0) {
|
| 376 |
+
return "These scores have been reported under different setups and by different organizations, which may explain some of the variation seen across reports."
|
| 377 |
+
}
|
| 378 |
+
if (variant > 0) {
|
| 379 |
+
return "These scores have been reported under different evaluation setups, which may explain some of the variation across reports."
|
| 380 |
+
}
|
| 381 |
+
if (crossParty > 0) {
|
| 382 |
+
return "Different organizations have reported notably different numbers for the same model on this benchmark."
|
| 383 |
+
}
|
| 384 |
+
if (noCrossPartyChecks) {
|
| 385 |
+
return "No independent third-party reports are available to cross-check the developer's numbers."
|
| 386 |
+
}
|
| 387 |
+
return null
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
// ---------------------------------------------------------------------------
|
| 391 |
+
// Helpers used by the existing policySummary lede in benchmark-detail.tsx,
|
| 392 |
+
// re-exported so the inline copy can be replaced.
|
| 393 |
+
// ---------------------------------------------------------------------------
|
| 394 |
+
|
| 395 |
+
/**
|
| 396 |
+
* Pull derived-tag categories off a flat list of evaluations. Useful when the
|
| 397 |
+
* caller already has the per-result entries grouped in the page.
|
| 398 |
+
*/
|
| 399 |
+
export function collectReportedCategoriesFromEvals(
|
| 400 |
+
evaluations: readonly BenchmarkEvaluation[],
|
| 401 |
+
resolveCategory: (e: BenchmarkEvaluation) => string | null | undefined
|
| 402 |
+
): string[] {
|
| 403 |
+
const seen = new Set<string>()
|
| 404 |
+
const out: string[] = []
|
| 405 |
+
for (const evaluation of evaluations) {
|
| 406 |
+
const cat = resolveCategory(evaluation)
|
| 407 |
+
if (!cat) continue
|
| 408 |
+
if (!seen.has(cat)) {
|
| 409 |
+
seen.add(cat)
|
| 410 |
+
out.push(cat)
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
return out
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
/** "Reasoning, Knowledge, Agentic" β for compact category badge rows. */
|
| 417 |
+
export function formatPolicyBucketsCompact(tags: readonly string[]): string {
|
| 418 |
+
return bucketCategories(tags).join(" Β· ")
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
/** Pretty-print a derivedTag for the rare case the policy bucket falls
|
| 422 |
+
* through (we still want a friendly word, not snake_case). */
|
| 423 |
+
export function formatTagAsPolicyLabel(tag: string): string {
|
| 424 |
+
return formatTagLabel(tag)
|
| 425 |
+
}
|