Summary
Contributor cards currently combine metrics with different definitions and then present them as one ranking:
points and tier can come from Open Design's GitHub-weighted score.
rank can come from Vaunt's repository contributions ordering.
totalContributors can be only the users fetched before the bounded Vaunt lookup stops.
topPercent is nevertheless computed as rank / totalContributors.
This can produce a card whose displayed points do not explain its rank and whose percentile denominator is a partial page rather than a complete cohort.
I am not proposing a particular contributor's rank or a new scoring policy here. The linked card is only a concrete reproduction of a data-provenance issue that can affect any contributor whose weighted score differs from Vaunt's contribution count.
Reproduction
Generated card/comment:
At card generation time, the Open Design weighted score was:
first merged PR 30
8 later merged PRs 96
1 reviewed PR 4
2 opened issues 10
20 commented threads 20
---
total 160
The Vaunt lookup returned contributions: 30 and ordinal position 89. The first response contained 100 records, 96 after the Worker filtered bots, and still had a next_cursor.
Data flow
Points/tier
resolveCurrentScore() chooses the maximum of Vaunt contributions, GitHub-weighted activity, and the event floor:
|
export function resolveCurrentScore(args: { |
|
vauntScore?: number; |
|
stats: ContributorStats; |
|
existing?: ContributorStateEntry | null; |
|
context: EventContext; |
|
}): { currentScore: number; sources: { vauntRankScore: number; githubWeighted: number; state: number; event: number } } { |
|
const vauntRankScore = args.vauntScore ?? 0; |
|
const githubWeighted = weightedScoreFromStats(args.stats); |
|
const event = eventScoreFloor(args.context, args.stats); |
|
const state = 0; |
|
|
|
return { |
|
currentScore: Math.max(vauntRankScore, githubWeighted, state, event), |
|
sources: { vauntRankScore, githubWeighted, state, event }, |
|
}; |
The Vaunt API calls its field contributions; its public object documentation describes it as the "number of contributions":
https://docs.vaunt.dev/api/objects/index.html#contributor-details
The Worker currently renames this value to score / vauntRankScore, although Open Design's configured point actions are a separate weighted metric.
Rank
Whenever the target is found in Vaunt, processRelay() prefers the Vaunt ordinal rank even if currentScore came from the GitHub-weighted score:
|
const { currentScore, sources } = resolveCurrentScore({ |
|
vauntScore: vauntLookup.score?.score, |
|
stats, |
|
existing, |
|
context, |
|
}); |
|
const currentTier = tierFromPoints(currentScore); |
|
const localRank = localRankFromScores(contributorScores, context.actor.login, currentScore); |
|
const rank = vauntLookup.score?.rank ?? localRank.rank; |
|
const totalContributors = vauntLookup.score?.rank |
|
? Math.max(vauntLookup.totalContributors, rank) |
|
: localRank.totalContributors; |
|
const decision = shouldAnnounce(currentTier.key, existing); |
|
|
|
const card: CardModel = { |
|
username: context.actor.login, |
|
avatarUrl: context.actor.avatarUrl ?? "", |
|
rank, |
|
totalContributors, |
|
topPercent: topPercent(rank, totalContributors), |
|
points: currentScore, |
Cohort size / percentile
The bounded lookup intentionally stops after finding the target:
|
if (!payload.next_cursor || payload.next_cursor === cursor) break; |
|
if (match) break; |
|
if (pages >= MAX_PAGES) { |
|
console.warn("Vaunt API lookup page limit reached", { owner, repo, login, pages, totalFetched: seen.size }); |
|
break; |
|
} |
|
if (!match && minHumanScore < MIN_SIGNAL_SCORE) break; |
|
cursor = payload.next_cursor; |
|
} |
|
|
|
return { |
|
score: match ? { ...match, totalFetched: seen.size } : null, |
|
totalContributors: seen.size, |
The regression test also codifies that a response with next_cursor may return totalContributors: 2 after fetching only the first page:
|
it("stops paging after finding the target", async () => { |
|
const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
|
const url = new URL(String(input)); |
|
const after = url.searchParams.get("after"); |
|
const payload = after === "page-2" |
|
? { |
|
data: [ |
|
{ name: "carol", type: "User", contributions: 20 }, |
|
{ name: "dave", type: "User", contributions: 12 }, |
|
], |
|
} |
|
: { |
|
data: [ |
|
{ name: "alice", type: "User", contributions: 100 }, |
|
{ name: "bob", type: "User", contributions: 50 }, |
|
], |
|
next_cursor: "page-2", |
|
}; |
|
|
|
return new Response(JSON.stringify(payload), { |
|
status: 200, |
|
headers: { "content-type": "application/json" }, |
|
}); |
|
}); |
|
globalThis.fetch = fetchMock as typeof fetch; |
|
|
|
const lookup = await fetchVauntContributorLookup("nexu-io", "open-design", "bob"); |
|
|
|
expect(lookup.score).toMatchObject({ login: "bob", score: 50, rank: 2 }); |
|
expect(lookup.totalContributors).toBe(2); |
|
expect(fetchMock).toHaveBeenCalledTimes(1); |
That bounded lookup is reasonable for Worker latency and API safety. The issue is that seen.size is named and rendered as the complete contributor total, and then used to compute a percentile:
|
export function rankSummary(rank: number, totalContributors: number): string { |
|
return `Rank #${rank.toLocaleString()} among ${fuzzyContributorCount(totalContributors)} contributors`; |
|
} |
|
|
|
export function topPercent(rank: number, totalContributors: number): number { |
|
const rawTopPercent = (rank / Math.max(1, totalContributors)) * 100; |
|
return Math.min(99, rawTopPercent); |
Expected invariant
A card should not imply that score, tier, rank, denominator, and percentile belong to one metric unless they use:
- the same scoring definition; and
- a clearly defined, complete cohort.
If a bounded lookup cannot know the full cohort size, the card should not present the fetched count as the total or derive a percentile from it.
Possible safe direction
Without removing the existing lookup bounds:
- Preserve metric provenance, for example
weighted_score vs vaunt_contributions.
- Return lookup completeness explicitly (
fetchedContributors, cohortComplete) rather than naming every partial count totalContributors.
- Show
topPercent and among N only when the cohort is complete and the rank is comparable to the displayed score.
- Otherwise show a source-labelled rank only (for example
Vaunt rank #89) or omit ranking until a complete/materialized leaderboard is available.
I would be happy to implement the agreed behavior in focused PRs. Before changing the certificate layout, could maintainers confirm:
- whether the public rank is intended to use Vaunt's contribution ordering or Open Design's weighted points; and
- what population should count as the rank/percentile cohort?
Summary
Contributor cards currently combine metrics with different definitions and then present them as one ranking:
pointsand tier can come from Open Design's GitHub-weighted score.rankcan come from Vaunt's repositorycontributionsordering.totalContributorscan be only the users fetched before the bounded Vaunt lookup stops.topPercentis nevertheless computed asrank / totalContributors.This can produce a card whose displayed points do not explain its rank and whose percentile denominator is a partial page rather than a complete cohort.
I am not proposing a particular contributor's rank or a new scoring policy here. The linked card is only a concrete reproduction of a data-provenance issue that can affect any contributor whose weighted score differs from Vaunt's contribution count.
Reproduction
Generated card/comment:
160Praxiteles#89 among 96 contributorsTop 92.7%At card generation time, the Open Design weighted score was:
The Vaunt lookup returned
contributions: 30and ordinal position89. The first response contained 100 records, 96 after the Worker filtered bots, and still had anext_cursor.Data flow
Points/tier
resolveCurrentScore()chooses the maximum of Vauntcontributions, GitHub-weighted activity, and the event floor:open-design-contributor-card/src/scoring.ts
Lines 48 to 62 in 84eef08
The Vaunt API calls its field
contributions; its public object documentation describes it as the "number of contributions":https://docs.vaunt.dev/api/objects/index.html#contributor-details
The Worker currently renames this value to
score/vauntRankScore, although Open Design's configured point actions are a separate weighted metric.Rank
Whenever the target is found in Vaunt,
processRelay()prefers the Vaunt ordinal rank even ifcurrentScorecame from the GitHub-weighted score:open-design-contributor-card/src/index.ts
Lines 105 to 125 in 84eef08
Cohort size / percentile
The bounded lookup intentionally stops after finding the target:
open-design-contributor-card/src/vaunt.ts
Lines 94 to 106 in 84eef08
The regression test also codifies that a response with
next_cursormay returntotalContributors: 2after fetching only the first page:open-design-contributor-card/tests/vaunt.test.ts
Lines 12 to 42 in 84eef08
That bounded lookup is reasonable for Worker latency and API safety. The issue is that
seen.sizeis named and rendered as the complete contributor total, and then used to compute a percentile:open-design-contributor-card/src/rank.ts
Lines 9 to 15 in 84eef08
Expected invariant
A card should not imply that score, tier, rank, denominator, and percentile belong to one metric unless they use:
If a bounded lookup cannot know the full cohort size, the card should not present the fetched count as the total or derive a percentile from it.
Possible safe direction
Without removing the existing lookup bounds:
weighted_scorevsvaunt_contributions.fetchedContributors,cohortComplete) rather than naming every partial counttotalContributors.topPercentandamong Nonly when the cohort is complete and the rank is comparable to the displayed score.Vaunt rank #89) or omit ranking until a complete/materialized leaderboard is available.I would be happy to implement the agreed behavior in focused PRs. Before changing the certificate layout, could maintainers confirm: