Skip to content

Contributor cards derive points and rank from different metrics and treat a partial Vaunt page as the full cohort #2

Description

@roian6

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:

  1. the same scoring definition; and
  2. 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:

  1. Preserve metric provenance, for example weighted_score vs vaunt_contributions.
  2. Return lookup completeness explicitly (fetchedContributors, cohortComplete) rather than naming every partial count totalContributors.
  3. Show topPercent and among N only when the cohort is complete and the rank is comparable to the displayed score.
  4. 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions