Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,24 +168,37 @@ function rankPerChainForBench(
if (!bestPerChain || !b.dimensions?.chain) return out;
const liveSorted = rankProviders(b);
if (liveSorted.length === 0) return out;
// Set of provider slugs that returned data per chain, populated by
// spec.ts. When present, we restrict per-chain ranks to that set so
// a Solana-only provider doesn't get a phantom chip on Base/BNB.
// Falls back to "everyone on the aggregate list" only when the bench
// hasn't stashed this — e.g. older cached entries from a v3 deploy.
const providersPerChain = (b as { providersPerChain?: Record<string, string[]> })
.providersPerChain;
for (const chain of b.dimensions.chain) {
if (chain.value === "all") continue;
const leader = bestPerChain[chain.value];
if (!leader) continue;
const presentSet = providersPerChain?.[chain.value]
? new Set(providersPerChain[chain.value].map((s) => s.toLowerCase()))
: undefined;
const scoped = presentSet
? liveSorted.filter((r) => presentSet.has(r.slug.toLowerCase()))
: liveSorted;
const perProvider = new Map<string, { rank: number; totalRanked: number }>();
const leaderLc = leader.slug.toLowerCase();
const leaderIdx = liveSorted.findIndex((r) => r.slug.toLowerCase() === leaderLc);
liveSorted.forEach((r, idx) => {
const leaderIdx = scoped.findIndex((r) => r.slug.toLowerCase() === leaderLc);
scoped.forEach((r, idx) => {
const lc = r.slug.toLowerCase();
if (lc === leaderLc) {
perProvider.set(lc, { rank: 1, totalRanked: liveSorted.length });
perProvider.set(lc, { rank: 1, totalRanked: scoped.length });
return;
}
// Anyone ranked above the leader in the unfiltered set drops by one
// slot here (since the leader skips ahead of them on this chain).
const rankOnChain =
leaderIdx !== -1 && idx < leaderIdx ? idx + 2 : idx + 1;
perProvider.set(lc, { rank: rankOnChain, totalRanked: liveSorted.length });
perProvider.set(lc, { rank: rankOnChain, totalRanked: scoped.length });
});
out[chain.value] = perProvider;
}
Expand Down
9 changes: 8 additions & 1 deletion src/lib/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const KEY_PREFIX = "ocb:snap:v1:";
* new field don't try to deserialize old-shape values. The Zod schema
* below would also reject those, but the version prefix lets us
* invalidate without writing strict-mode parsers. */
const SCHEMA_VERSION = 2 as const;
const SCHEMA_VERSION = 3 as const;

// Minimal runtime payload. Editorial metadata isn't snapshotted because
// it lives in YAML and is rebuilt from the spec on every read.
Expand All @@ -59,6 +59,7 @@ const SnapshotSchema = z.object({
extras: z.any(),
bestPerChain: z.record(z.string(), z.any()).optional(),
worstPerChain: z.record(z.string(), z.any()).optional(),
providersPerChain: z.record(z.string(), z.array(z.string())).optional(),
});

export type SnapshotPayload = {
Expand All @@ -68,6 +69,7 @@ export type SnapshotPayload = {
lastRunAt: string;
bestPerChain?: Record<string, ProviderResult>;
worstPerChain?: Record<string, ProviderResult>;
providersPerChain?: Record<string, string[]>;
};

function isConfigured(): boolean {
Expand Down Expand Up @@ -178,6 +180,9 @@ export async function readSnapshot(
worstPerChain: parsed.data.worstPerChain as
| Record<string, ProviderResult>
| undefined,
providersPerChain: parsed.data.providersPerChain as
| Record<string, string[]>
| undefined,
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
Expand Down Expand Up @@ -208,5 +213,7 @@ export function snapshotFromBenchmark(b: Benchmark): SnapshotPayload {
// snapshot serves the raw placeholder string to the page.
bestPerChain: b.bestPerChain,
worstPerChain: b.worstPerChain,
providersPerChain: (b as { providersPerChain?: Record<string, string[]> })
.providersPerChain,
};
}
26 changes: 21 additions & 5 deletions src/lib/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ const loadBenchmarkUnfilteredCached = unstable_cache(
// v3: added bestPerChain + worstPerChain stash; cached objects from
// v2 deploys lack those fields, which made `{{best_name:chain:X}}`
// placeholders fall through to the raw token on the rendered page.
["bench-unfiltered-v3"],
// v4: added providersPerChain so per-chain rank chips only render for
// providers that actually returned data on that chain (Solana-only
// providers no longer get phantom chips on Base/BNB).
["bench-unfiltered-v4"],
{ revalidate: 60, tags: ["benchmarks"] },
);

Expand Down Expand Up @@ -362,6 +365,7 @@ async function specToBenchmark(
// with no Prom data just doesn't show up in bestPerChain.
let bestPerChain: Record<string, ProviderResult> | undefined;
let worstPerChain: Record<string, ProviderResult> | undefined;
let providersPerChain: Record<string, string[]> | undefined;
// Compute per-chain leaders for BOTH unfiltered and filtered views.
// Filtered variants are pre-fetched by the page (one per chain × region
// combo) and end up in the RSC payload; their findings/seo_intro/faq
Expand All @@ -378,26 +382,37 @@ async function specToBenchmark(
chainValues.map(async (chain) => {
const chainSpec = applyDimensionsToSpec(spec, { chain });
const chainLive = await tryLoadLive(chainSpec, true);
if (!chainLive) return [chain, undefined, undefined] as const;
if (!chainLive) {
return [chain, undefined, undefined, [] as string[]] as const;
}
for (const r of chainLive.results) r.availability = "live";
const liveForChain = liveProviderResults(chainLive.results);
const slugs = liveForChain.map((r) => r.slug);
if (liveForChain.length === 0) {
return [chain, undefined, undefined] as const;
return [chain, undefined, undefined, slugs] as const;
}
const sorted = [...liveForChain].sort((a, b) =>
spec.higher_is_better ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50,
);
return [chain, sorted[0], sorted[sorted.length - 1]] as const;
return [
chain,
sorted[0],
sorted[sorted.length - 1],
slugs,
] as const;
}),
);
const bests: Record<string, ProviderResult> = {};
const worsts: Record<string, ProviderResult> = {};
for (const [chain, leader, trailer] of perChainEntries) {
const providers: Record<string, string[]> = {};
for (const [chain, leader, trailer, slugs] of perChainEntries) {
if (leader) bests[chain] = leader;
if (trailer) worsts[chain] = trailer;
if (slugs.length > 0) providers[chain] = slugs;
}
if (Object.keys(bests).length > 0) bestPerChain = bests;
if (Object.keys(worsts).length > 0) worstPerChain = worsts;
if (Object.keys(providers).length > 0) providersPerChain = providers;
}

// Resolve {{p50:slug}} / {{best_name}} / {{count}} etc. placeholders
Expand All @@ -408,6 +423,7 @@ async function specToBenchmark(
...live,
bestPerChain,
worstPerChain,
providersPerChain,
});
// Persist a snapshot of the runtime data so a future cold start
// during a Prom blackout can still render this bench. Only the
Expand Down
7 changes: 7 additions & 0 deletions src/types/benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ export type Benchmark = {
* the `{{worst_name:chain:X}}` / `{{worst_p50:chain:X}}` editorial
* placeholders. */
worstPerChain?: Record<string, ProviderResult>;
/** Set of provider slugs that returned live data on each chain. Same
* key set as `bestPerChain`. Powers the per-chain rank chips on
* /products/[slug] so chain-restricted providers (Solana-only like
* GMGN) only get chips on chains they actually compete on, instead
* of inheriting their aggregate position on every chain in the
* bench. */
providersPerChain?: Record<string, string[]>;
findings: string[];
methodology: string[];
source: string;
Expand Down
Loading