Add standard deviation display for predicted ratings

- Calculate and store standard deviation during rating prediction
- Add std_dev column to players database table
- Display standard deviation tooltip on hover over predicted rating
- Show rating range (±std_dev) tooltip on hover over current rating
- Update tooltips dynamically when ratings are refreshed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Samuel Enocsson
2025-10-14 17:48:21 +02:00
parent d46f045815
commit 10d1f88a58
2 changed files with 225 additions and 22 deletions
+36 -11
View File
@@ -54,6 +54,7 @@ function initializeDatabase() {
const hasLastRoundUpdate = columns.some(col => col.name === 'last_round_update');
const hasPredictedRating = columns.some(col => col.name === 'predicted_rating');
const hasStdDev = columns.some(col => col.name === 'std_dev');
if (!hasLastRoundUpdate) {
console.log('Adding last_round_update column to players table...');
@@ -80,6 +81,19 @@ function initializeDatabase() {
}
});
}
if (!hasStdDev) {
console.log('Adding std_dev column to players table...');
db.run(`
ALTER TABLE players ADD COLUMN std_dev INTEGER DEFAULT NULL
`, (err) => {
if (err) {
console.error('Error adding std_dev column:', err.message);
} else {
console.log('Successfully added std_dev column');
}
});
}
});
});
@@ -533,8 +547,12 @@ async function getPlayerDataFromDB(pdgaNumber) {
// Use stored predicted_rating if available, otherwise calculate it from round history
let predictedRating = cachedPlayer.predicted_rating;
let stdDev = cachedPlayer.std_dev;
if (!predictedRating || predictedRating === 0) {
predictedRating = await getPredictedRatingFromDB(pdgaNumber);
// After calculation, re-fetch to get the updated std_dev
const updatedPlayer = await getPlayerFromDB(pdgaNumber);
stdDev = updatedPlayer?.std_dev;
}
return {
@@ -542,7 +560,8 @@ async function getPlayerDataFromDB(pdgaNumber) {
name: cachedPlayer.name,
rating: cachedPlayer.current_rating,
ratingChange: cachedPlayer.rating_change,
predictedRating: predictedRating > 0 ? predictedRating : null
predictedRating: predictedRating > 0 ? predictedRating : null,
stdDev: stdDev > 0 ? stdDev : null
};
}
return null; // No data in DB
@@ -653,7 +672,7 @@ async function getPredictedRatingFromDB(pdgaNumber) {
const result = calculatePredictedRating(roundRatings);
// Save the calculated prediction to database
await savePredictedRatingToDB(pdgaNumber, result.rating);
await savePredictedRatingToDB(pdgaNumber, result.rating, result.stdDev);
return result.rating;
}
@@ -664,11 +683,11 @@ async function getPredictedRatingFromDB(pdgaNumber) {
}
}
function savePredictedRatingToDB(pdgaNumber, predictedRating) {
function savePredictedRatingToDB(pdgaNumber, predictedRating, stdDev = null) {
return new Promise((resolve, reject) => {
db.run(
'UPDATE players SET predicted_rating = ? WHERE pdga_number = ?',
[predictedRating, pdgaNumber],
'UPDATE players SET predicted_rating = ?, std_dev = ? WHERE pdga_number = ?',
[predictedRating, stdDev, pdgaNumber],
function(err) {
if (err) reject(err);
else resolve();
@@ -1483,15 +1502,19 @@ function calculatePredictedRating(roundRatings) {
const sum = weightedRatings.reduce((sum, r) => sum + r, 0);
const average = sum / weightedRatings.length;
const finalRating = Math.round(average);
// Calculate standard deviation of the weighted ratings
const stdDev = calculateStandardDeviation(weightedRatings);
debugLog.push('🎯 FINAL CALCULATION:');
debugLog.push(` Sum: ${sum}`);
debugLog.push(` Count: ${weightedRatings.length}`);
debugLog.push(` Average: ${average.toFixed(1)}`);
debugLog.push(` Standard Deviation: ${stdDev.toFixed(1)}`);
debugLog.push(` Final Rating: ${finalRating}`);
debugLog.push('=== END PDGA CALCULATION ===');
return { rating: finalRating, debugLog };
return { rating: finalRating, stdDev: Math.round(stdDev), debugLog };
}
function calculateStandardDeviation(ratings) {
@@ -2773,7 +2796,7 @@ app.post('/api/refresh-round-history/:pdgaNumber', async (req, res) => {
const result = calculatePredictedRating(roundsForPrediction);
// Save the predicted rating to database for persistence
await savePredictedRatingToDB(pdgaNumber, result.rating);
await savePredictedRatingToDB(pdgaNumber, result.rating, result.stdDev);
// Count official vs new rounds
const officialCount = allRounds.filter(r => r.source === 'official').length;
@@ -2782,6 +2805,7 @@ app.post('/api/refresh-round-history/:pdgaNumber', async (req, res) => {
res.json({
success: true,
predictedRating: result.rating,
stdDev: result.stdDev,
debugLog: result.debugLog,
totalRounds: roundsForPrediction.length,
officialRounds: officialCount,
@@ -3319,10 +3343,11 @@ app.post('/api/predicted-rating/:pdgaNumber', async (req, res) => {
}));
const result = calculatePredictedRating(roundRatings);
res.json({
res.json({
pdgaNumber: parseInt(pdgaNumber),
predictedRating: result.rating,
stdDev: result.stdDev,
debugLog: result.debugLog
});
} catch (error) {