diff --git a/front_end/messages/en.json b/front_end/messages/en.json index cca809b189..4ddaa7a4da 100644 --- a/front_end/messages/en.json +++ b/front_end/messages/en.json @@ -320,6 +320,7 @@ "average": "Average", "score": "Score", "coverage": "Coverage", + "totalCoverage": "Total Coverage", "totalScore": "Total Score", "totalTake": "Total Take", "weightedAverageScore": "Weighted Average Score", @@ -524,6 +525,8 @@ "noMedals": "No medals yet", "leaderboard": "Leaderboard", "openLeaderboard": "Open Leaderboard", + "liveLeaderboardDisclaimer": "This is a live leaderboard; rankings may change on a daily basis.", + "legacyPeerDisclaimer": "The Peer Accuracy leaderboards used slightly different math before 2024. See details here.", "namesPrediction": "{username}'s Prediction", "includeMyForecast": "include your latest forecast in this comment", "privateComment": "private comment", diff --git a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_header.tsx b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_header.tsx index 54c127b591..6b2f123a2b 100644 --- a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_header.tsx +++ b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_header.tsx @@ -43,18 +43,18 @@ const LeaderboardHeader: FC = ({ filters }) => { }; return ( -
+
{category !== "all" && ( {t("leaderboards")} )} -
+
{category !== "all" ? (
{t(RANKING_CATEGORIES[category].translationKey)} @@ -64,7 +64,7 @@ const LeaderboardHeader: FC = ({ filters }) => { )}
-
+
{RANKING_CATEGORIES[category].explanation}
@@ -95,13 +95,13 @@ const LeaderboardHeader: FC = ({ filters }) => {
)}
-
+
{/* comments and questionWriting leaderboards only exist for 1-year durations, so no selector is shown */} {durations && duration && ["all", "peer", "baseline"].includes(category) && (
- + {t("duration:")} = ({ filters }) => { )} {periods && year && (
- + {t("timePeriod")} = ({ filters }) => {
)}
+ {Number(year) + Number(duration) > 2024 && ( +
+ {t("liveLeaderboardDisclaimer")} +
+ )} + {category === "peer" && Number(year) + Number(duration) < 2024 && ( +
+ {t.rich("legacyPeerDisclaimer", { + link: (chunks) => ( + {chunks} + ), + })} +
+ )}
); }; diff --git a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/index.tsx b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/index.tsx index 6d40ce43b5..7bbcd7c443 100644 --- a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/index.tsx +++ b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/index.tsx @@ -68,6 +68,7 @@ const LeaderboardTable: FC = ({ year={year} duration={duration} category={category} + scoreType={leaderboardDetails.score_type} /> )} @@ -77,6 +78,11 @@ const LeaderboardTable: FC = ({ {category === "comments" ? t("comments") : t("questions")} + {leaderboardDetails.score_type === "peer_global" && ( + + {t("totalCoverage")} + + )} {t("score")} {!!entriesToDisplay.length ? ( @@ -84,6 +90,7 @@ const LeaderboardTable: FC = ({ = ({ year={year} duration={duration} category={category} + scoreType={leaderboardDetails.score_type} /> )} diff --git a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/table_row.tsx b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/table_row.tsx index 02f59ed15d..dca8fcdb3b 100644 --- a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/table_row.tsx +++ b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/leaderboard_table/table_row.tsx @@ -3,7 +3,11 @@ import Link from "next/link"; import { FC } from "react"; import { Href } from "@/types/navigation"; -import { CategoryKey, LeaderboardEntry } from "@/types/scoring"; +import { + CategoryKey, + LeaderboardEntry, + LeaderboardType, +} from "@/types/scoring"; import { abbreviatedNumber } from "@/utils/number_formatters"; import MedalIcon from "../../../components/medal_icon"; @@ -16,13 +20,26 @@ import { type Props = { rowEntry: LeaderboardEntry; + scoreType: LeaderboardType; href: Href; isUserRow?: boolean; }; -const LeaderboardRow: FC = ({ rowEntry, href, isUserRow = false }) => { - const { user, aggregation_method, rank, contribution_count, score, medal } = - rowEntry; +const LeaderboardRow: FC = ({ + rowEntry, + scoreType, + href, + isUserRow = false, +}) => { + const { + user, + aggregation_method, + rank, + coverage, + contribution_count, + score, + medal, + } = rowEntry; return ( = ({ rowEntry, href, isUserRow = false }) => { {abbreviatedNumber(contribution_count, 3, 0)} + {scoreType == "peer_global" && ( + + + {abbreviatedNumber(coverage, 3, 0)} + + + )} = ({ userEntry, year, duration, category, + scoreType, }) => { // only show this row for users who are logged in and have any ranking data // in this category @@ -109,7 +138,14 @@ export const UserLeaderboardRow: FC = ({ ? "/medals" : `/contributions/?${SCORING_CATEGORY_FILTER}=${category}&${CONTRIBUTIONS_USER_FILTER}=${userEntry.user!.id}&${SCORING_YEAR_FILTER}=${year}&${SCORING_DURATION_FILTER}=${duration}`; - return ; + return ( + + ); }; export default LeaderboardRow; diff --git a/front_end/src/app/(main)/(leaderboards)/ranking_categories.tsx b/front_end/src/app/(main)/(leaderboards)/ranking_categories.tsx index b9af5cd569..2fb39bc036 100644 --- a/front_end/src/app/(main)/(leaderboards)/ranking_categories.tsx +++ b/front_end/src/app/(main)/(leaderboards)/ranking_categories.tsx @@ -43,13 +43,13 @@ export const RANKING_CATEGORIES: Record< explanation: ( Peer Accuracy measures how accurate a user was compared - to others. Users are ranked by the average of their{" "} - Peer scores. If a user - forecast - {" N<40"} questions, then their average score includes (40-N) questions - with a 0 score. This lowers the chance of a user getting lucky on a few - questions and winning a medal. Learn more{" "} - here. + to others. Users are ranked by the sum of their{" "} + Peer scores, divided by the + sum of their Coverages. This + creates a weighted average, where each prediction is counted + proportionally to how long it was standing. To reduce the impact of + luck, all forecasters start with a prior of 30 questions with a score of + 0. Learn more here. ), },