diff --git a/src/app/answers/[slug]/page.tsx b/src/app/answers/[slug]/page.tsx index 1483f6bd..e189a1b6 100644 --- a/src/app/answers/[slug]/page.tsx +++ b/src/app/answers/[slug]/page.tsx @@ -8,7 +8,7 @@ import { cleanLeftoverTokens } from "@/lib/answers-template"; import { Breadcrumb } from "@/components/breadcrumb"; import { Pill } from "@/components/pill"; import { ProviderLogo } from "@/components/provider-logo"; -import { fmtValue, fmtUnit, unitSuffix } from "@/lib/format"; +import { fmtAsOfUtc, fmtValue, fmtUnit, unitSuffix } from "@/lib/format"; import { isRegion } from "@/lib/brand"; import { SITE } from "@/data/site"; import { @@ -92,6 +92,7 @@ export default async function AnswerPage({ // YAML's slug reference) fall through to a neutral fallback via // cleanLeftoverTokens so a placeholder string never reaches the SERP. const render = (s: string) => cleanLeftoverTokens(renderTemplate(s, bench)); + const asOfUtc = fmtAsOfUtc(bench.lastRunAt); const shortAnswer = render(ans.short_answer); const intro = render(ans.intro); const methodology = render(ans.methodology); @@ -187,6 +188,17 @@ export default async function AnswerPage({
+ Data as of , + refreshed continuously. +
+ )}
diff --git a/src/app/api/citable/route.ts b/src/app/api/citable/route.ts
index e1283e1e..1440ab17 100644
--- a/src/app/api/citable/route.ts
+++ b/src/app/api/citable/route.ts
@@ -3,6 +3,7 @@ import { getBenchmarks } from "@/data/benchmarks";
import { SITE } from "@/data/site";
import { AllBenchmarksDraftError } from "@/lib/spec";
import { citeBundle, fieldValue, leader, headlineSentence } from "@/lib/citation";
+import { valueInDeclaredUnit } from "@/lib/format";
import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit";
export const runtime = "nodejs";
@@ -53,6 +54,11 @@ export async function GET(req: Request) {
// from an undersized field. The headline sentence is rewritten to
// "insufficient data" by headlineSentence above.
const insufficient = b.dataConfidence === "insufficient";
+ // `value` and `leader.value` are published in the declared `unit`.
+ // Latency benches with unit "s" store ms internally (fmtUnit
+ // convention); valueInDeclaredUnit converts so the JSON never claims
+ // 645 seconds for a 645 ms head lag.
+ const raw = insufficient ? null : fieldValue(b);
return {
slug: b.slug,
title: b.title,
@@ -60,12 +66,16 @@ export async function GET(req: Request) {
metric: b.metric,
unit: b.unit,
status: b.status,
- value: insufficient ? null : fieldValue(b),
+ value: raw == null ? null : valueInDeclaredUnit(raw, b.unit),
leader:
insufficient
? null
: top
- ? { name: top.name, slug: top.slug, value: top.value }
+ ? {
+ name: top.name,
+ slug: top.slug,
+ value: valueInDeclaredUnit(top.value, b.unit),
+ }
: null,
sampleSize: b.sampleSize,
expectedN: b.expectedN,
diff --git a/src/app/api/openapi.json/route.ts b/src/app/api/openapi.json/route.ts
index 0821f40e..d7abc397 100644
--- a/src/app/api/openapi.json/route.ts
+++ b/src/app/api/openapi.json/route.ts
@@ -14,8 +14,8 @@ export async function GET() {
openapi: "3.1.0",
info: {
title: "OpenChainBench API",
- version: "1.0.0",
- description: SITE.description,
+ version: "1.1.0",
+ description: `${SITE.description} An MCP server (Streamable HTTP, POST only) is also available at ${SITE.url}/api/mcp/mcp exposing list_benchmarks, get_benchmark and query_prom tools; see ${SITE.url}/mcp for install instructions.`,
license: { name: "CC-BY-4.0", url: "https://creativecommons.org/licenses/by/4.0/" },
},
servers: [{ url: SITE.url }],
@@ -67,6 +67,50 @@ export async function GET() {
},
},
},
+ "/api/llm-context": {
+ get: {
+ summary:
+ "All benchmarks, rankings and methodology as one Markdown document, ready to paste into a system prompt.",
+ operationId: "get_llm_context",
+ responses: {
+ "200": {
+ description: "OK",
+ content: { "text/markdown": { schema: { type: "string" } } },
+ },
+ "503": { description: "Benchmarks temporarily unavailable" },
+ },
+ },
+ },
+ "/api/freshness": {
+ get: {
+ summary:
+ "Lightweight freshness probe: last resolved data timestamp (epoch ms) per benchmark slug.",
+ operationId: "get_freshness",
+ responses: {
+ "200": {
+ description: "OK",
+ content: {
+ "application/json": {
+ schema: { $ref: "#/components/schemas/Freshness" },
+ },
+ },
+ },
+ },
+ },
+ },
+ "/api/search/featured": {
+ get: {
+ summary:
+ "Slim featured-leaders payload (live leaders + trending benches) that feeds the site search dialog.",
+ operationId: "get_featured_leaders",
+ responses: {
+ "200": {
+ description: "OK",
+ content: { "application/json": { schema: { type: "object" } } },
+ },
+ },
+ },
+ },
},
components: {
schemas: {
@@ -85,7 +129,11 @@ export async function GET() {
title: { type: "string" },
metric: { type: "string" },
unit: { type: "string" },
- value: { type: "number", nullable: true },
+ value: {
+ type: "number",
+ nullable: true,
+ description: "Current leading value, expressed in `unit`.",
+ },
headline: { type: "string" },
url: { type: "string", format: "uri" },
api: { type: "string", format: "uri" },
@@ -93,12 +141,27 @@ export async function GET() {
asOf: { type: "string", format: "date-time" },
},
},
+ Freshness: {
+ type: "object",
+ properties: {
+ now: { type: "integer", description: "Server epoch ms at response time." },
+ freshness: {
+ type: "object",
+ additionalProperties: { type: "integer" },
+ description: "Benchmark slug to last data timestamp (epoch ms).",
+ },
+ },
+ },
Stat: {
type: "object",
properties: {
slug: { type: "string" },
title: { type: "string" },
- value: { type: "number", nullable: true },
+ value: {
+ type: "number",
+ nullable: true,
+ description: "Current leading value, expressed in `unit`.",
+ },
unit: { type: "string" },
rankings: { type: "array" },
sparkline: { type: "array", items: { type: "number" } },
diff --git a/src/app/api/stat/[slug]/route.ts b/src/app/api/stat/[slug]/route.ts
index b53c5767..973f1e16 100644
--- a/src/app/api/stat/[slug]/route.ts
+++ b/src/app/api/stat/[slug]/route.ts
@@ -9,6 +9,7 @@ import {
leader,
sparklineFor,
} from "@/lib/citation";
+import { valueInDeclaredUnit } from "@/lib/format";
import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit";
import { SLUG_RE } from "@/lib/slug";
@@ -45,6 +46,9 @@ export async function GET(
const top = leader(b);
const insufficient = b.dataConfidence === "insufficient";
+ // Publish value + leader.value in the declared unit. Unit "s" benches
+ // store ms internally (fmtUnit convention); do not leak that here.
+ const raw = insufficient ? null : fieldValue(b);
const payload = {
slug: b.slug,
title: b.title,
@@ -59,8 +63,11 @@ export async function GET(
// leader; the headline is rewritten by headlineSentence so the
// agent / journalist reads "insufficient data" instead of quoting
// a number drawn from undersized samples.
- value: insufficient ? null : fieldValue(b),
- leader: insufficient ? null : top,
+ value: raw == null ? null : valueInDeclaredUnit(raw, b.unit),
+ leader:
+ insufficient || !top
+ ? null
+ : { ...top, value: valueInDeclaredUnit(top.value, b.unit) },
rankings: b.results
.filter((r) => r.ms.p50 > 0)
// Drop "insufficient" rows from the machine-readable ranking too:
diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx
index 80f60996..2876e39e 100644
--- a/src/app/benchmarks/[slug]/page.tsx
+++ b/src/app/benchmarks/[slug]/page.tsx
@@ -26,6 +26,7 @@ import {
isInsufficient,
leader,
} from "@/lib/citation";
+import { valueInDeclaredUnit } from "@/lib/format";
import { capDescription } from "@/lib/seo-text";
import { getBenchCreatedAt } from "@/lib/seo/bench-dates";
import { SITE } from "@/data/site";
@@ -332,7 +333,9 @@ export default async function BenchmarkPage({
metric: benchmark.metric,
metricUnit: benchmark.unit,
leaderName: currentLeader.name,
- leaderValue: currentLeader.value,
+ // Observation.measuredValue must be in unitText. Unit "s" benches
+ // store ms internally; convert before publishing to JSON-LD.
+ leaderValue: valueInDeclaredUnit(currentLeader.value, benchmark.unit),
temporalCoverage: `${getBenchCreatedAt(benchmark.slug).toISOString()}/${benchmark.lastRunAt}`,
observationDate: benchmark.lastRunAt,
url: benchmarkUrl,
diff --git a/src/app/llms.txt/route.ts b/src/app/llms.txt/route.ts
index 05d85aa4..3069ec6e 100644
--- a/src/app/llms.txt/route.ts
+++ b/src/app/llms.txt/route.ts
@@ -46,7 +46,7 @@ export async function GET() {
lines.push(`- [Citable index (JSON)](${SITE.url}/api/citable): flat list of all benchmarks with current values, ready for one-shot lookup.`);
lines.push(`- [LLM context (Markdown)](${SITE.url}/api/llm-context): all ${benches.length} benchmarks + rankings + methodology in one Markdown blob, ready to paste into a system prompt.`);
lines.push(`- [OpenAPI schema](${SITE.url}/api/openapi.json): full description of every endpoint.`);
- lines.push(`- [MCP server](${SITE.url}/api/mcp/mcp): exposes \`list_benchmarks\`, \`get_benchmark\`, \`query_prom\` tools + \`openchainbench://benchmark/{slug}\` resources over Streamable HTTP. See [/mcp](${SITE.url}/mcp) for install instructions (Claude Desktop, Cursor, generic clients).`);
+ lines.push(`- [MCP server docs](${SITE.url}/mcp): install instructions (Claude Desktop, Cursor, generic clients) for the MCP server at ${SITE.url}/api/mcp/mcp, which exposes \`list_benchmarks\`, \`get_benchmark\`, \`query_prom\` tools + \`openchainbench://benchmark/{slug}\` resources over Streamable HTTP (JSON-RPC via POST; the endpoint is not browsable with GET).`);
lines.push("");
lines.push(`## Benchmarks`);
lines.push("");
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e7f7834f..8677658c 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -5,7 +5,7 @@ import { getBenchmarksSafe } from "@/data/benchmarks";
import { HeroRadar } from "@/components/hero-radar";
import { HomeBenchTable } from "@/components/home-bench-table";
import { LiveDashboard } from "@/components/live/dashboard";
-import { GLOBAL_DATASET_JSONLD } from "@/lib/dataset-jsonld";
+import { buildGlobalDatasetJsonLd } from "@/lib/dataset-jsonld";
import { safeJsonLd } from "@/lib/jsonld";
export const revalidate = 60;
@@ -35,6 +35,15 @@ export const metadata: Metadata = {
export default async function HomePage() {
const benchmarks = await getBenchmarksSafe();
+ // Same timestamp source the bench pages use for Dataset.dateModified:
+ // the harness lastRunAt (real data timestamp, not build time). The
+ // newest one across the catalog dates the site-wide Dataset node.
+ const latestRunAt = benchmarks
+ .map((b) => b.lastRunAt)
+ .filter((iso) => iso && !Number.isNaN(new Date(iso).getTime()))
+ .sort()
+ .pop();
+
return (