Skip to content

feat: explainable, better-calibrated developer score (#82) - #85

Merged
sajeetharan merged 2 commits into
sajeetharan:mainfrom
HereIsMuhammad:feat/explainable-score
Aug 7, 2026
Merged

feat: explainable, better-calibrated developer score (#82)#85
sajeetharan merged 2 commits into
sajeetharan:mainfrom
HereIsMuhammad:feat/explainable-score

Conversation

@HereIsMuhammad

Copy link
Copy Markdown
Contributor

Closes #82

What changed

Makes the 0-100 developer score explainable and reviews its presentation, per the issue's acceptance criteria:

  • Score explanation, everywhere the score appears. A short, neutral note now sits beside the score in both the leaderboard (Leaderboard.jsx) and the profile detail panel (DetailPanel.jsx), plus a title tooltip on every score badge.
  • Dimension breakdown. lib/scoring.js exports explainScore(), which returns each of the six dimensions with its label, weight, normalized value, and point contribution. The profile panel renders this as a labeled bar list under the existing radar chart (ScoreExplanation component).
  • Relative normalization and missing SO documentation. SCORE_METHODOLOGY in lib/scoring.js holds the copy used across the UI: the score is described as a relative ranking signal calibrated against the current dataset, not an absolute measure. When a profile has no linked Stack Overflow activity, scoreAll() and explainScore() now expose that fact (hasSO, redistributed) and the UI surfaces a note that the 25% SO weight was redistributed to GitHub based dimensions.
  • Data freshness. githubFetchedAt and soFetchedAt are stamped in the two fetch scripts. build-dataset.js derives a metricsUpdatedAt field from them. lib/format.js adds formatRelativeTime() and isStaleData(), and the profile panel shows "Metrics last refreshed", flagging anything older than 30 days.
  • Score distribution and calibration. build-dataset.js now imports the real lib/scoring.js instead of a second, separately maintained copy of the scoring math (that duplication meant the shipped dataset and client side scoring could silently drift apart, which is fixed as part of this change). It also logs a min/p25/median/p75/max distribution and the count of SO sparse profiles on every rebuild.
  • Neutral copy. Removed "elite" from Globe.jsx's score tier color comments. All new UI copy avoids implying the score measures overall developer ability.
  • Mobile. The new breakdown UI reuses the existing chart section width and compact typography, so no new breakpoints were needed.

Not included in this PR

  • Automated tests for the score explanation (criterion in the issue). Adding a test runner requires updating package.json and package-lock.json together, and I didn't want to hand edit the lockfile without running npm install to generate it properly (this repo's CI uses npm ci, which fails on a lockfile or package.json mismatch). Happy to follow up with a lib/scoring.test.js (covering complete, missing SO, sparse, and stale data cases) in a separate PR once a maintainer confirms a preferred test runner. explainScore() and getScorePercentile() were written to be easy to unit test in isolation.
  • data/developers.json (the real production dataset) isn't regenerated here since that requires GITHUB_TOKEN and SO API credentials I don't have. Only data/developers-sample.json (local dev data) is updated, with staggered metricsUpdatedAt values so fresh, stale, and missing freshness states are all visible locally.

Testing

  • Manually verified explainScore(), scoreAll(), getScorePercentile(), formatRelativeTime(), and isStaleData() behavior for complete data, missing SO data, and stale or missing timestamps.
  • Syntax checked every changed .js and .jsx file (no errors) and validated data/developers-sample.json as JSON.
  • package.json and package-lock.json are untouched, so npm ci in CI is unaffected.
  • Not yet visually verified with npm run dev. Would appreciate a look at the new breakdown UI and freshness labels before merge.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

@HereIsMuhammad is attempting to deploy a commit to the sajeetharan's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes DevGlobe’s 0–100 developer score more interpretable by adding neutral explanatory copy, surfacing a per-dimension breakdown, and introducing a “metrics last refreshed” indicator intended to improve calibration and transparency across the leaderboard and profile detail views.

Changes:

  • Adds score methodology copy + tooltips in the leaderboard and profile header, plus a new “Score Breakdown” UI section.
  • Refactors dataset scoring to reuse lib/scoring.js, adds score percentile + SO-weight-redistribution metadata, and logs score distribution stats during dataset rebuilds.
  • Introduces data freshness timestamps in fetch scripts and new relative-time/staleness formatting helpers, and updates the sample dataset with freshness fields.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
styles/main.css Adds styling for score notes, score explanation breakdown rows, and freshness/stale states.
scripts/fetch-stackoverflow.js Stamps soFetchedAt when SO metrics are captured.
scripts/fetch-github.js Stamps githubFetchedAt when GitHub metrics are captured.
scripts/build-dataset.js Switches to shared scoring/ranking modules; derives metricsUpdatedAt; logs score distribution stats.
lib/scoring.js Adds dimension metadata + methodology copy; exports explain/percentile helpers; enriches scoreAll() output with weights/flags.
lib/format.js Adds relative-time formatting and staleness detection for metrics freshness display.
data/developers-sample.json Adds metricsUpdatedAt examples and reformats sample JSON.
components/Leaderboard.jsx Adds contextual score explanation and badge tooltips/ARIA labeling.
components/Globe.jsx Updates score tier comments/copy to be more neutral.
components/DetailPanel.jsx Renders score methodology, dimension breakdown list, percentile line, and freshness status in profile panel.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/scoring.js
Comment thread lib/format.js
Comment on lines +35 to +41
export function isStaleData(isoString, staleDays = STALE_DATA_DAYS) {
if (!isoString) return true;
const date = new Date(isoString);
if (Number.isNaN(date.getTime())) return true;
const diffDays = (Date.now() - date.getTime()) / (1000 * 60 * 60 * 24);
return diffDays > staleDays;
}
Comment on lines +208 to +210
const freshLabel = formatRelativeTime(dev.metricsUpdatedAt);
const stale = isStaleData(dev.metricsUpdatedAt);

Comment thread scripts/build-dataset.js
Comment thread lib/scoring.js
Comment on lines 116 to +120
score += dimensions[key] * weight;
}

return { total: Math.round(score * 100), dimensions };
return { total: Math.round(score * 100), dimensions, weights, hasSO };
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@sajeetharan
sajeetharan merged commit 5d951c3 into sajeetharan:main Aug 7, 2026
2 of 3 checks passed
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.

Make the 0-100 developer score explainable and better calibrated

3 participants