feat: players page redesign — deltas, KPI tiles, sparklines, expanded row #9

Merged
shcizo merged 27 commits from feat/shared-visual-layer-topbar-4 into main 2026-05-21 16:14:01 +02:00
Showing only changes of commit a7562e9b47 - Show all commits
+30 -3
View File
@@ -4,6 +4,28 @@ const { fetchPlayerDataHTTP, parsePlayerData } = require('../scrapers/player-htt
const { calculatePredictedRating } = require('./rating-calculator');
const logger = require('../logger');
// Derives previous-month rating and the delta to it. Prefers PDGA's reported
// rating_change (canonical), falls back to our own monthly snapshots when
// rating_change is missing — common for players whose latest scrape failed.
function deriveMonthlyDeltas(rating, rawRatingChange, monthlyHistory) {
if (rating != null && rawRatingChange != null) {
return { lastMonthRating: rating - rawRatingChange, ratingChange: rawRatingChange };
}
if (rating != null && monthlyHistory && monthlyHistory.length >= 1) {
// The "last month" snapshot depends on whether current_rating is already in
// history. If equal, current is the most recent entry — last month is the one
// before it. If not, current is newer than history — the latest entry IS last month.
const lastIdx = monthlyHistory.length - 1;
const lastMonth = (monthlyHistory[lastIdx] === rating)
? (monthlyHistory.length >= 2 ? monthlyHistory[lastIdx - 1] : null)
: monthlyHistory[lastIdx];
if (lastMonth != null) {
return { lastMonthRating: lastMonth, ratingChange: rating - lastMonth };
}
}
return { lastMonthRating: null, ratingChange: rawRatingChange };
}
async function getPlayerDataFromDB(pdgaNumber, { includeMonthlyHistory = true } = {}) {
try {
const cachedPlayer = await getPlayerFromDB(pdgaNumber);
@@ -19,7 +41,7 @@ async function getPlayerDataFromDB(pdgaNumber, { includeMonthlyHistory = true }
}
const rating = cachedPlayer.current_rating;
const ratingChange = cachedPlayer.rating_change;
const rawRatingChange = cachedPlayer.rating_change;
const resolvedPredicted = predictedRating > 0 ? predictedRating : null;
const resolvedStdDev = stdDev > 0 ? stdDev : null;
@@ -28,6 +50,8 @@ async function getPlayerDataFromDB(pdgaNumber, { includeMonthlyHistory = true }
? await getMonthlyHistory(cachedPlayer.pdga_number)
: [];
const { lastMonthRating, ratingChange } = deriveMonthlyDeltas(rating, rawRatingChange, monthlyHistory);
return {
pdgaNumber: cachedPlayer.pdga_number,
name: cachedPlayer.name,
@@ -35,8 +59,7 @@ async function getPlayerDataFromDB(pdgaNumber, { includeMonthlyHistory = true }
ratingChange,
predictedRating: resolvedPredicted,
stdDev: resolvedStdDev,
// previous month's official rating (null when either value is missing)
lastMonthRating: (rating != null && ratingChange != null) ? rating - ratingChange : null,
lastMonthRating,
// gap between next predicted update and current rating (null when either is missing)
deltaPredicted: (resolvedPredicted != null && rating != null) ? resolvedPredicted - rating : null,
monthlyHistory
@@ -166,6 +189,10 @@ async function getAllRatingsFromDB(progressCallback = null) {
if (playerData) {
playerData.monthlyHistory = monthlyHistoryMap.get(pdgaNumber) ?? [];
// Re-derive now that history is attached — bulk path skipped includeMonthlyHistory
const derived = deriveMonthlyDeltas(playerData.rating, player.rating_change, playerData.monthlyHistory);
playerData.lastMonthRating = derived.lastMonthRating;
playerData.ratingChange = derived.ratingChange;
ratings.push(playerData);
}