Key types
types/scoring/rules.ts
interface ScoringRuleset {
basePoints: BasePointRule[]; // { type: SignalType; points: number }
multipliers: MultiplierRule[]; // { kind; factor; appliesTo: SignalType[] }
dailyQuotas: DailyQuota[]; // { type: SignalType; limit: number }
diminishingReturns: DiminishingReturnConfig; // { weeklyThreshold; decayFactor; floorFraction }
zeroPointConditions: ZeroPointCondition[]; // { kind: ZeroPointKind; enabled: boolean }
spamPenalty: number; // default: -12
prClosedNoMergePenalty: number; // default: -10
}
type MultiplierKind = 'merged_pr_commit' | 'pr_linked_to_issue' | 'first_activity';
type ZeroPointKind =
| 'self_review'
| 'self_merge'
| 'bot_activity'
| 'issue_closed_no_pr'
| 'pr_closed_no_merge';types/scoring/leaderboard.ts
interface LeaderboardEntry {
userId: number;
userLogin: string;
score: number;
breakdown: ScoreBreakdown;
rank: number;
}
interface LeaderboardResponse {
entries: LeaderboardEntry[];
computedAt: string;
cacheStatus: 'fresh' | 'stale' | 'recomputed';
}
interface ScoreBreakdown {
commits: number;
pullRequests: number;
reviews: number;
issues: number;
comments: number;
penalties: number;
}types/scoring/aggregate.ts
interface RepositoryScore {
repoName: string;
score: number;
breakdown: ScoreBreakdown;
rank: number;
}
interface TeamScore {
teamSlug: string;
teamName: string;
score: number;
memberCount: number;
breakdown: ScoreBreakdown;
rank: number;
}
interface TeamMembership {
userId: number;
userLogin: string;
teamSlug: string;
teamName: string;
}types/api/leaderboards.ts
type LeaderboardScopeType = 'organization' | 'repository' | 'team';
type LeaderboardEntityType = 'contributor' | 'repository' | 'team';Module map
lib/scoring/
| File | Exports | Purpose |
|---|---|---|
engine.ts | computeScores(), buildLeaderboard(), computeSignalScore(), createScoringContext() | Core per-signal scoring, per-user aggregation, breakdown tracking |
aggregate.ts | aggregateByRepository(), aggregateByTeam() | Entity-type-specific aggregation |
normalize.ts | normalizeGitHubData() | Raw GitHub data → normalized Signal[] |
diminishing.ts | diminishingValue() | Diminishing returns formula |
rules.ts | defaultScoringRuleset() | Default configuration |
index.ts | Re-exports all public API | Barrel file |
lib/leaderboard/
| File | Exports | Purpose |
|---|---|---|
pipeline.ts | recomputeLeaderboard(), refreshLeaderboardInBackground(), resolveReposForScope() | Full pipeline orchestration |
parse-leaderboard-query.ts | parseLeaderboardQuery() | Validate and normalize API params |
cache.ts | isPrecomputedScoresFresh() | TTL check for computed scores |
resolve-installation.ts | resolveInstallationForOrganization() | Authenticated installation resolution |
map-snapshot-entries.ts | mapSnapshotsToLeaderboardEntries() | DB rows → LeaderboardEntry[] |
synthetic-user-id.ts | syntheticUserId() | Negative-space ID from login string |
request-cache.ts | getLeaderboardRequestCache(), setLeaderboardRequestCache() | In-memory LRU + Redis caching |
score.ts | scoreActivePresetForEntity(), scoreRemainingEntityTypes() | Per-preset scoring orchestration |
ingest.ts | runIngest() | GitHub data fetch + normalization + DB upsert |
lib/github/
| File | Exports | Purpose |
|---|---|---|
fetch-graphql.ts | fetchOrgScoringDataGraphQL(), fetchRepoActivityGraphQL(), fetchOrgTeamsDataGraphQL() | Data fetching with pagination and caching |
graphqlProxy.ts | executeGithubGraphql() | Typed allowlist proxy to GitHub GraphQL |
queries/ | Various query constants | Inline and imported GraphQL query documents |
lib/supabase/
| File | Exports | Purpose |
|---|---|---|
signals.ts | getSignalsForScoring(), upsertSignals(), deleteSignalsForInstallation() | Signals CRUD |
leaderboard-db.ts | getScoringRules(), writeComputedScores(), preset CRUD | Rules + computed scores + materializations |
repo-cache.ts | getRepoFetchLogByName(), upsertRepoFetchLogByName() | Per-repo fetch tracking |
Rate limits
These constants govern how much data we fetch from GitHub:
| Constant | Value | What it limits |
|---|---|---|
MAX_COMMITS_PER_REPO | 100 | Commits per repo per page |
MAX_PULLS_PER_REPO | 50 | Pull requests per repo per page |
MAX_ISSUES_PER_REPO | 50 | Issues per repo per page |
REPO_CONCURRENCY | 6 | Concurrent repo fetches |
DEFAULT_LOOKBACK_DAYS | 90 | Default time window for data fetch |
Reviews are implicitly bounded by PR page limits.
Error handling
- Signal upsert uses
ignoreDuplicates: true— re-runs are idempotent - Snapshot write errors are logged but don’t fail the pipeline
- Background refresh errors are logged silently
- Admin-only endpoints return 403 for non-admin users
- Missing presets fall back to
defaultScoringRuleset()in memory - Per-repo fetch errors are isolated per-repo (other repos continue)
Edge cases
- Self-review / self-merge: Always zero points (configurable)
- Bot activity: Zero points (configurable, detected via
author.typefrom GitHub API) - Issue closed without linked PR: Zero points (checked via
hasLinkedPRmetadata) - User in multiple teams: Full score contributes to each team
- Empty org with no repos or signals: Returns empty entries array
- Date-range queries: Always cold-computed (cache skipped), filtered at DB level
- Team with no members: Not included in results
- Beyond-quota signals: Signals exceeding
dailyQuotaslimit earn zero points - Synthetic user IDs: Bots without GitHub
databaseIdget negative integer IDs to avoid collision with real positive GitHub user IDs
Related
- Overview — architecture, pipeline, entity types
- Scoring Engine — signal types, algorithm, multipliers, quotas
- Configuration & Presets — default ruleset, custom presets, API
Last updated on