feat: explainable, better-calibrated developer score (#82) - #85
Merged
sajeetharan merged 2 commits intoAug 7, 2026
Merged
Conversation
|
@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. |
Contributor
There was a problem hiding this comment.
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 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 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #82
What changed
Makes the 0-100 developer score explainable and reviews its presentation, per the issue's acceptance criteria:
Leaderboard.jsx) and the profile detail panel (DetailPanel.jsx), plus atitletooltip on every score badge.lib/scoring.jsexportsexplainScore(), 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 (ScoreExplanationcomponent).SCORE_METHODOLOGYinlib/scoring.jsholds 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()andexplainScore()now expose that fact (hasSO,redistributed) and the UI surfaces a note that the 25% SO weight was redistributed to GitHub based dimensions.githubFetchedAtandsoFetchedAtare stamped in the two fetch scripts.build-dataset.jsderives ametricsUpdatedAtfield from them.lib/format.jsaddsformatRelativeTime()andisStaleData(), and the profile panel shows "Metrics last refreshed", flagging anything older than 30 days.build-dataset.jsnow imports the reallib/scoring.jsinstead 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.Globe.jsx's score tier color comments. All new UI copy avoids implying the score measures overall developer ability.Not included in this PR
package.jsonandpackage-lock.jsontogether, and I didn't want to hand edit the lockfile without runningnpm installto generate it properly (this repo's CI usesnpm ci, which fails on a lockfile or package.json mismatch). Happy to follow up with alib/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()andgetScorePercentile()were written to be easy to unit test in isolation.data/developers.json(the real production dataset) isn't regenerated here since that requiresGITHUB_TOKENand SO API credentials I don't have. Onlydata/developers-sample.json(local dev data) is updated, with staggeredmetricsUpdatedAtvalues so fresh, stale, and missing freshness states are all visible locally.Testing
explainScore(),scoreAll(),getScorePercentile(),formatRelativeTime(), andisStaleData()behavior for complete data, missing SO data, and stale or missing timestamps..jsand.jsxfile (no errors) and validateddata/developers-sample.jsonas JSON.package.jsonandpackage-lock.jsonare untouched, sonpm ciin CI is unaffected.npm run dev. Would appreciate a look at the new breakdown UI and freshness labels before merge.