From 0464f55e652f1067e168036a8b772a8068bc8fd7 Mon Sep 17 00:00:00 2001 From: Muhammad Ahmed Date: Fri, 7 Aug 2026 16:49:52 +0500 Subject: [PATCH 1/2] feat: explainable, better-calibrated developer score (#82) --- components/DetailPanel.jsx | 64 ++- components/Globe.jsx | 10 +- components/Leaderboard.jsx | 14 +- data/developers-sample.json | 823 ++++++++++++++++++++++++++------- lib/format.js | 35 ++ lib/scoring.js | 131 +++++- scripts/build-dataset.js | 70 ++- scripts/fetch-github.js | 5 +- scripts/fetch-stackoverflow.js | 5 +- styles/main.css | 97 ++++ 10 files changed, 1028 insertions(+), 226 deletions(-) diff --git a/components/DetailPanel.jsx b/components/DetailPanel.jsx index 1d64c11..c33431d 100644 --- a/components/DetailPanel.jsx +++ b/components/DetailPanel.jsx @@ -2,7 +2,8 @@ import React, { useEffect, useRef, useState } from 'react'; import * as d3 from 'd3'; -import { formatNum } from '../lib/format.js'; +import { formatNum, formatRelativeTime, isStaleData } from '../lib/format.js'; +import { DIMENSIONS, SCORE_METHODOLOGY } from '../lib/scoring.js'; export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMount = false, claimSuccess = false }) { const [fullData, setFullData] = useState(null); @@ -79,7 +80,12 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou
šŸ“ {dev.location || 'Unknown location'}
- Score: {dev.score}/100 + + Score: {dev.score}/100 + {dev.globalRank && ( )}
+

+ {SCORE_METHODOLOGY.short} +

GitHub ↗ {merged.soUserId && ( @@ -145,6 +154,7 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou

Score Breakdown

+
@@ -190,6 +200,54 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou ); } +function ScoreExplanation({ dev }) { + const dimensions = dev.scoreDimensions; + const weights = dev.scoreWeights; + if (!dimensions || !weights) return null; + + const freshLabel = formatRelativeTime(dev.metricsUpdatedAt); + const stale = isStaleData(dev.metricsUpdatedAt); + + return ( +
+

{SCORE_METHODOLOGY.short}

+ {dev.scoreHasSO === false && ( +

{SCORE_METHODOLOGY.noSO}

+ )} + +
    + {DIMENSIONS.map(({ key, label, description }) => { + const normalized = dimensions[key] || 0; + const weight = weights[key] || 0; + const points = Math.round(normalized * weight * 100); + return ( +
  • +
    + {label} + {points} pts +
    +
    +
    +
    + {Math.round(weight * 100)}% weight +
  • + ); + })} +
+ + {typeof dev.scorePercentile === 'number' && ( +

+ Higher than {dev.scorePercentile}% of developers currently indexed by DevGlobe. +

+ )} + +

+ {freshLabel ? `Metrics last refreshed ${freshLabel}${stale ? ' — may be out of date' : ''}.` : 'Metrics freshness unknown.'} +

+
+ ); +} + function StatCard({ label, value, className = '' }) { return (
@@ -514,4 +572,4 @@ function CardModal({ dev, claimSuccess, onClose }) {
); -} +} \ No newline at end of file diff --git a/components/Globe.jsx b/components/Globe.jsx index 5ffaa9d..12c681b 100644 --- a/components/Globe.jsx +++ b/components/Globe.jsx @@ -17,12 +17,12 @@ const COUNTRY_GEOJSON_URLS = [ const POLYGON_ALTITUDE = 0.003; const POLYGON_ALTITUDE_ACTIVE = 0.009; -// Score-based color gradient: blue → green → gold → red +// Score-based color gradient (visual tiering only, not a judgment of skill) function getScoreColor(score) { - if (score >= 80) return '#fbbf24'; // gold — elite - if (score >= 60) return '#34d399'; // emerald — strong - if (score >= 40) return '#3b82f6'; // blue — solid - return '#6366f1'; // indigo — emerging + if (score >= 80) return '#fbbf24'; // gold — top of the indexed range + if (score >= 60) return '#34d399'; // emerald — upper-mid range + if (score >= 40) return '#3b82f6'; // blue — mid range + return '#6366f1'; // indigo — lower range } function featureName(feat) { diff --git a/components/Leaderboard.jsx b/components/Leaderboard.jsx index 71cd4f5..17ade0d 100644 --- a/components/Leaderboard.jsx +++ b/components/Leaderboard.jsx @@ -3,6 +3,7 @@ import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react'; import { formatNum } from '../lib/format.js'; import { extractCountry, normalizeCountry, countryKey } from '../lib/country.js'; +import { SCORE_METHODOLOGY } from '../lib/scoring.js'; const ITEM_HEIGHT = 62; const BUFFER = 10; @@ -165,6 +166,11 @@ export default function Leaderboard({
+ {sortBy === 'score' && ( +

+ ā“˜ Score is relative to this dataset, not an absolute rating — tap a profile for the full breakdown. +

+ )}