Skip to content

fix(ranking): rank multi-chain benches by contested-chain wins, not the mix - #2280

Merged
Flotapponnier merged 1 commit into
devfrom
fix/rank-by-contested-chain-wins
Sep 8, 2026
Merged

fix(ranking): rank multi-chain benches by contested-chain wins, not the mix#2280
Flotapponnier merged 1 commit into
devfrom
fix/rank-by-contested-chain-wins

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

The bug

On a bench that declares chain dimensions, the headline ranking used the cross-chain aggregate. That aggregate is a mix, not a comparison: it rewards a provider for the chains it happens to be measured on. A provider measured on exactly one chain that nobody else reported could top the board without ever beating anyone.

Five live benches shipped that way. Measured against the staging API, all 15 chain-dimensioned live benches:

bench aggregate leader chains it is measured on most chains led
rpc-capabilities Binance 1 PublicNode, 6
wallet-labels-coverage XRPScan 1 Serialized, 4
token-quote-coverage Jupiter 1 Mobula, 2
bridge-fee Squid Router 1 Across, 1
perp-liq-rate Lighter 1 Paradex, 1

Bench 008 in detail:

provider old rank aggregate chains measured chains led
StellarExpert 1 80.08% 1 (uncontested) 0 contested
XRPScan 2 79.84% 1 (uncontested) 0 contested
Serialized 3 76.98% 5 4
Mobula 5 46.75% 8 3

stellar, xrp and bitcoin have exactly one provider measured on them.

The change

rankedCandidates sorts by contested-chain wins first, aggregate value only as a tiebreak. A chain counts toward the total only when at least two providers reported data on it, so an uncontested chain awards nothing.

Guarded by the per-chain stashes (bestPerChain, providersPerChain), which materialize/load.ts populates only on the unfiltered view. A chain-filtered variant carries neither, so ?chain=bnb keeps ranking by value exactly as before, and a bench with no chain dimensions is untouched.

providers.ts reuses the same comparator. The two surfaces disagreeing is what produced #3 of 8 sitting next to five chain-leadership chips on one row of /products/serialized.

Known trade-off

A provider with one contested win now ranks above a provider with none and a higher aggregate figure — on 008, TonAPI (35.6%, 1 win) above XRPScan (79.8%, 0 contested chains). That is what ranking on head-to-head record means, and the win count is visible on the row. The alternative considered was scoring on the mean over contested chains only, which produces no inversions but drops uncontested providers off the board entirely.

Blast radius

rankedCandidates feeds leader(), fieldValue(), /api/stat, MCP, llm-context, JSON-LD, OG images and the templated headline sentences. The published leader changes for the five benches above, and the daily Parquet snapshot's headlines table will follow from the next run.

Rule documented in /methodology, section II.

241 tests pass (4 new), typecheck clean, eslint clean on touched files, 218 specs valid.

🤖 Generated with Claude Code

…he mix

On a bench with chain dimensions the cross-chain aggregate is a mix, not a
comparison. Ranking on it alone let a provider measured on one chain that
nobody else reported finish above a provider that led several contested
ones. Five live benches shipped that way:

  rpc-capabilities        Binance 1st on 1 chain, PublicNode led 6
  wallet-labels-coverage  XRPScan 1st on 1 chain, Serialized led 4
  token-quote-coverage    Jupiter 1st on 1 chain, Mobula led 2
  bridge-fee              Squid Router 1st on 1 chain
  perp-liq-rate           Lighter 1st on 1 chain

rankedCandidates now sorts by contested-chain wins first and uses the
aggregate value only to break ties. A chain counts only when at least two
providers reported on it, so an uncontested chain awards nothing: you do
not win a race you ran alone.

Guarded by the per-chain stashes, which materialize/load.ts populates only
on the unfiltered view. A chain-filtered variant has none, so ?chain=bnb
keeps ranking by value as before.

providers.ts reuses the same ordering. The two surfaces disagreeing is what
put "#3 of 8" next to five chain-leadership chips on the same bench row.

Known and accepted: a provider with one contested win now ranks above one
with none and a higher aggregate figure (TonAPI over XRPScan on 008). That
is what ranking on head-to-head record means; the win count is on the row.

Rule documented in methodology, section II.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Flotapponnier
Flotapponnier merged commit e5950ff into dev Sep 8, 2026
1 check failed
@Flotapponnier
Flotapponnier deleted the fix/rank-by-contested-chain-wins branch September 8, 2026 20:22
Flotapponnier added a commit that referenced this pull request Sep 9, 2026
…2281)

Replaces the global chain-wins sort from #2280, which fixed the ordering but
broke the reading of it. Ranking on a key the reader cannot see in the column
produced tables that no longer descend: 79.88% at rank 4 on 008, and a 74 ms
leader at rank 3 on rpc-capabilities where lower is better.

The defect was never the sort key. It is that the aggregate includes chains
where a provider had no competitor, so the fix belongs on the value: with
`score_scope: contested_chains` a bench is scored only on chains carrying at
least two measured providers, and a provider with none of those leaves the
ranked field (still visible on its own chain tab). One quantity on screen,
ordering follows from it.

Opt-in per bench and named for the property, not the bench, so any bench
whose chain set contains uncontested chains is a candidate and the rule is
readable in the public YAML. Enabled on wallet-labels-coverage only:
stellar, xrp and bitcoin carry one measured provider each there.

load.ts already fetched every provider's per-chain value inside its per-chain
loop and discarded all but the leader; it now keeps them.

Four other live benches qualify and are deliberately left untouched pending
review: rpc-capabilities, token-quote-coverage, bridge-fee, perp-liq-rate.

Residual limitation documented in methodology: providers are still averaged
over the different subsets of contested chains they cover.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant