From c5e3b23386c9aac27aa337cf72dcc73d0a83285b Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sat, 11 Jul 2026 21:55:34 +0200 Subject: [PATCH] geo: publish values in declared unit, fix HF parquet link, freshness stamps, wider openapi --- src/app/answers/[slug]/page.tsx | 14 +++++- src/app/api/citable/route.ts | 14 +++++- src/app/api/openapi.json/route.ts | 71 ++++++++++++++++++++++++++++-- src/app/api/stat/[slug]/route.ts | 11 ++++- src/app/benchmarks/[slug]/page.tsx | 5 ++- src/app/llms.txt/route.ts | 2 +- src/app/page.tsx | 15 ++++++- src/components/benchmark-body.tsx | 14 +----- src/lib/dataset-jsonld.ts | 23 ++++++++-- src/lib/format.ts | 24 ++++++++++ 10 files changed, 163 insertions(+), 30 deletions(-) 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({
{shortAnswer} + {/* Dated freshness stamp right next to the quotable sentence, + mirroring the "Data as of ... UTC" line on the bench pages. + Answer engines quote a claim far more readily when the date + of measurement sits beside it. Uses the referenced bench's + lastRunAt (real data timestamp), not build time. */} + {asOfUtc && ( +

+ 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 (

{/* Site-wide schema.org/Dataset entry. Points Google Dataset Search, @@ -46,7 +55,9 @@ export default async function HomePage() {