Compare commits

...
1 Commits
Author SHA1 Message Date
François Rozet 1afe4189cb Add finer ranking levels 2023-06-01 20:24:21 +02:00
5 changed files with 68 additions and 59 deletions
+25 -29
View File
@@ -1,5 +1,10 @@
function expsf(x, lambda = 1) { function exponential_cdf(x) {
return 2 ** (-lambda * x); return 1 - 2 ** -x;
}
function log_normal_cdf(x) {
// approximation
return x / (1 + x);
} }
/** /**
@@ -13,7 +18,7 @@ function expsf(x, lambda = 1) {
* @param {number} params.repos Total number of repos. * @param {number} params.repos Total number of repos.
* @param {number} params.stars The number of stars. * @param {number} params.stars The number of stars.
* @param {number} params.followers The number of followers. * @param {number} params.followers The number of followers.
* @returns {{level: string, score: number}}} The users rank. * @returns {{level: string, percentile: number}}} The users rank.
*/ */
function calculateRank({ function calculateRank({
all_commits, all_commits,
@@ -24,15 +29,15 @@ function calculateRank({
stars, stars,
followers, followers,
}) { }) {
const COMMITS_MEAN = all_commits ? 1000 : 250, const COMMITS_MEDIAN = all_commits ? 1000 : 250,
COMMITS_WEIGHT = 2; COMMITS_WEIGHT = 2;
const PRS_MEAN = 50, const PRS_MEDIAN = 50,
PRS_WEIGHT = 3; PRS_WEIGHT = 3;
const ISSUES_MEAN = 25, const ISSUES_MEDIAN = 25,
ISSUES_WEIGHT = 1; ISSUES_WEIGHT = 1;
const STARS_MEAN = 250, const STARS_MEDIAN = 50,
STARS_WEIGHT = 4; STARS_WEIGHT = 4;
const FOLLOWERS_MEAN = 25, const FOLLOWERS_MEDIAN = 10,
FOLLOWERS_WEIGHT = 1; FOLLOWERS_WEIGHT = 1;
const TOTAL_WEIGHT = const TOTAL_WEIGHT =
@@ -42,30 +47,21 @@ function calculateRank({
STARS_WEIGHT + STARS_WEIGHT +
FOLLOWERS_WEIGHT; FOLLOWERS_WEIGHT;
const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];
const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"];
const rank = const rank =
(COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) + 1 -
PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) + (COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) +
ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) + PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) +
STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) + ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) +
FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) / STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) +
TOTAL_WEIGHT; FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) /
TOTAL_WEIGHT;
const RANK_S_PLUS = 0.025; const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)];
const RANK_S = 0.1;
const RANK_A_PLUS = 0.25;
const RANK_A = 0.5;
const RANK_B_PLUS = 0.75;
const level = (() => { return { level: level, percentile: rank * 100 };
if (rank <= RANK_S_PLUS) return "S+";
if (rank <= RANK_S) return "S";
if (rank <= RANK_A_PLUS) return "A+";
if (rank <= RANK_A) return "A";
if (rank <= RANK_B_PLUS) return "B+";
return "B";
})();
return { level, score: rank * 100 };
} }
export { calculateRank }; export { calculateRank };
+2 -3
View File
@@ -209,9 +209,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
hide_rank ? 0 : 150, hide_rank ? 0 : 150,
); );
// the better user's score the the rank will be closer to zero so // the lower the user's percentile the better
// subtracting 100 to get the progress in 100% const progress = 100 - rank.percentile;
const progress = 100 - rank.score;
const cssStyles = getStyles({ const cssStyles = getStyles({
titleColor, titleColor,
ringColor, ringColor,
+1 -1
View File
@@ -192,7 +192,7 @@ const fetchStats = async (
totalIssues: 0, totalIssues: 0,
totalStars: 0, totalStars: 0,
contributedTo: 0, contributedTo: 0,
rank: { level: "B", score: 0 }, rank: { level: "C", percentile: 100 },
}; };
let res = await statsFetcher(username); let res = await statsFetcher(username);
+1 -1
View File
@@ -22,7 +22,7 @@ export type StatsData = {
totalIssues: number; totalIssues: number;
totalStars: number; totalStars: number;
contributedTo: number; contributedTo: number;
rank: { level: string; score: number }; rank: { level: string; percentile: number };
}; };
export type Lang = { export type Lang = {
+39 -25
View File
@@ -2,7 +2,7 @@ import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js"; import { calculateRank } from "../src/calculateRank.js";
describe("Test calculateRank", () => { describe("Test calculateRank", () => {
it("new user gets B rank", () => { it("new user gets C rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -13,10 +13,24 @@ describe("Test calculateRank", () => {
stars: 0, stars: 0,
followers: 0, followers: 0,
}), }),
).toStrictEqual({ level: "B", score: 100 }); ).toStrictEqual({ level: "C", percentile: 100 });
}); });
it("average user gets A rank", () => { it("beginner user gets B- rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 125,
prs: 25,
issues: 10,
repos: 0,
stars: 25,
followers: 5,
}),
).toStrictEqual({ level: "B-", percentile: 69.333868386557 });
});
it("median user gets B+ rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -24,13 +38,13 @@ describe("Test calculateRank", () => {
prs: 50, prs: 50,
issues: 25, issues: 25,
repos: 0, repos: 0,
stars: 250, stars: 50,
followers: 25, followers: 10,
}), }),
).toStrictEqual({ level: "A", score: 50 }); ).toStrictEqual({ level: "B+", percentile: 50 });
}); });
it("average user gets A rank (include_all_commits)", () => { it("average user gets B+ rank (include_all_commits)", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: true, all_commits: true,
@@ -38,13 +52,13 @@ describe("Test calculateRank", () => {
prs: 50, prs: 50,
issues: 25, issues: 25,
repos: 0, repos: 0,
stars: 250, stars: 50,
followers: 25, followers: 10,
}), }),
).toStrictEqual({ level: "A", score: 50 }); ).toStrictEqual({ level: "B+", percentile: 50 });
}); });
it("more than average user gets A+ rank", () => { it("advanced user gets A rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -52,13 +66,13 @@ describe("Test calculateRank", () => {
prs: 100, prs: 100,
issues: 50, issues: 50,
repos: 0, repos: 0,
stars: 500, stars: 200,
followers: 50, followers: 40,
}), }),
).toStrictEqual({ level: "A+", score: 25 }); ).toStrictEqual({ level: "A", percentile: 22.72727272727273 });
}); });
it("expert user gets S rank", () => { it("expert user gets A+ rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -66,23 +80,23 @@ describe("Test calculateRank", () => {
prs: 200, prs: 200,
issues: 100, issues: 100,
repos: 0, repos: 0,
stars: 1000, stars: 800,
followers: 100, followers: 160,
}), }),
).toStrictEqual({ level: "S", score: 6.25 }); ).toStrictEqual({ level: "A+", percentile: 6.082887700534744 });
}); });
it("ezyang gets S+ rank", () => { it("sindresorhus gets S rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
commits: 1000, commits: 1300,
prs: 4000, prs: 1500,
issues: 2000, issues: 4500,
repos: 0, repos: 0,
stars: 5000, stars: 600000,
followers: 2000, followers: 50000,
}), }),
).toStrictEqual({ level: "S+", score: 1.1363983154296875 }); ).toStrictEqual({ level: "S", percentile: 0.49947889605312934 });
}); });
}); });