diff --git a/index.html b/index.html
index 19611b9..c22b385 100644
--- a/index.html
+++ b/index.html
@@ -253,6 +253,64 @@
display: inline-flex;
align-items: center;
}
+ .debug-icon:hover {
+ opacity: 1 !important;
+ color: #007bff !important;
+ transform: scale(1.1);
+ }
+ .debug-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 10001;
+ display: none;
+ justify-content: center;
+ align-items: center;
+ }
+ .debug-content {
+ background: white;
+ border-radius: 8px;
+ padding: 20px;
+ max-width: 600px;
+ max-height: 80vh;
+ overflow-y: auto;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ }
+ .debug-header {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 15px;
+ color: #333;
+ border-bottom: 2px solid #007bff;
+ padding-bottom: 10px;
+ }
+ .debug-log {
+ font-family: 'Courier New', monospace;
+ background-color: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 4px;
+ padding: 15px;
+ font-size: 12px;
+ line-height: 1.4;
+ white-space: pre-line;
+ color: #495057;
+ }
+ .debug-close {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ font-size: 24px;
+ color: #999;
+ cursor: pointer;
+ background: none;
+ border: none;
+ }
+ .debug-close:hover {
+ color: #333;
+ }
@@ -272,6 +330,15 @@
+
+
+
diff --git a/server.js b/server.js
index c283401..eb89d7b 100644
--- a/server.js
+++ b/server.js
@@ -548,10 +548,10 @@ async function getPredictedRating(browser, pdgaNumber, retries = 2) {
try {
console.log(`Predicted rating attempt ${attempt}/${retries} for PDGA ${pdgaNumber}`);
const roundRatings = await getPlayerCompetitionRatings(browser, pdgaNumber);
- const predictedRating = calculatePredictedRating(roundRatings);
+ const result = calculatePredictedRating(roundRatings);
- if (predictedRating > 0) {
- return predictedRating;
+ if (result.rating > 0) {
+ return result.rating;
}
if (attempt < retries) {
@@ -579,10 +579,12 @@ async function getPredictedRatingFromDB(pdgaNumber) {
// Convert to the format expected by calculatePredictedRating
const roundRatings = roundHistory.map(round => ({
rating: round.rating,
- date: new Date(round.date)
+ date: new Date(round.date),
+ competition: round.competition_name || 'Unknown'
}));
- return calculatePredictedRating(roundRatings);
+ const result = calculatePredictedRating(roundRatings);
+ return result.rating;
}
return 0;
} catch (err) {
@@ -1242,19 +1244,20 @@ function getNextPDGAUpdateDate() {
}
function calculatePredictedRating(roundRatings) {
- console.log('\n=== PDGA RATING CALCULATION (Following Official Rules) ===');
+ const debugLog = [];
+ debugLog.push('=== PDGA RATING CALCULATION (Following Official Rules) ===');
if (!roundRatings || roundRatings.length === 0) {
- console.log('ā No rounds provided for prediction');
- return 0;
+ debugLog.push('ā No rounds provided for prediction');
+ return { rating: 0, debugLog };
}
- console.log(`š Starting with ${roundRatings.length} total rounds`);
+ debugLog.push(`š Starting with ${roundRatings.length} total rounds`);
// PDGA Simulation: Only include rounds that would be rated by next update
const nextUpdateDate = getNextPDGAUpdateDate();
- console.log(`šÆ PDGA Update Simulation: Next update date is ${nextUpdateDate.toDateString()}`);
- console.log(` Only including rounds played before ${nextUpdateDate.toDateString()}`);
+ debugLog.push(`šÆ PDGA Update Simulation: Next update date is ${nextUpdateDate.toDateString()}`);
+ debugLog.push(` Only including rounds played before ${nextUpdateDate.toDateString()}`);
// Sort all rounds by date (most recent first), but only include rounds before next update
const allSortedRounds = roundRatings
@@ -1262,25 +1265,25 @@ function calculatePredictedRating(roundRatings) {
.sort((a, b) => b.date - a.date);
if (allSortedRounds.length === 0) {
- console.log('ā No valid rounds after filtering for update date');
- return 0;
+ debugLog.push('ā No valid rounds after filtering for update date');
+ return { rating: 0, debugLog };
}
- console.log(`š After update date filter: ${allSortedRounds.length} rounds`);
+ debugLog.push(`š After update date filter: ${allSortedRounds.length} rounds`);
// PDGA Rule: Use rounds from 12 months prior to next update date
const twelveMonthsBeforeUpdate = new Date(nextUpdateDate);
twelveMonthsBeforeUpdate.setFullYear(twelveMonthsBeforeUpdate.getFullYear() - 1);
const mostRecentDate = allSortedRounds[0].date;
- console.log(`š
Most recent round: ${mostRecentDate.toDateString()}`);
- console.log(`š
12-month cutoff: ${twelveMonthsBeforeUpdate.toDateString()} (1 year before update)`);
+ debugLog.push(`š
Most recent round: ${mostRecentDate.toDateString()}`);
+ debugLog.push(`š
12-month cutoff: ${twelveMonthsBeforeUpdate.toDateString()} (1 year before update)`);
// Step 1: Get rounds from last 12 months before update
let eligibleRounds = allSortedRounds.filter(r => r.date >= twelveMonthsBeforeUpdate);
- console.log(`\nšļø 12-MONTH FILTERING:`);
- console.log(`ā
Rounds in last 12 months: ${eligibleRounds.length}`);
+ debugLog.push('šļø 12-MONTH FILTERING:');
+ debugLog.push(`ā
Rounds in last 12 months: ${eligibleRounds.length}`);
// PDGA Rule: If fewer than 8 rounds in 12 months, extend to 24 months before update
if (eligibleRounds.length < 8) {
@@ -1288,17 +1291,17 @@ function calculatePredictedRating(roundRatings) {
twentyFourMonthsBeforeUpdate.setFullYear(twentyFourMonthsBeforeUpdate.getFullYear() - 2);
eligibleRounds = allSortedRounds.filter(r => r.date >= twentyFourMonthsBeforeUpdate);
- console.log(`ā ļø Extended to 24 months before update (${twentyFourMonthsBeforeUpdate.toDateString()}) - now ${eligibleRounds.length} rounds`);
+ debugLog.push(`ā ļø Extended to 24 months before update (${twentyFourMonthsBeforeUpdate.toDateString()}) - now ${eligibleRounds.length} rounds`);
}
if (eligibleRounds.length === 0) {
- console.log('ā No eligible rounds found');
- return 0;
+ debugLog.push('ā No eligible rounds found');
+ return { rating: 0, debugLog };
}
- console.log(`\nš ELIGIBLE ROUNDS: ${eligibleRounds.length}`);
+ debugLog.push(`š ELIGIBLE ROUNDS: ${eligibleRounds.length}`);
eligibleRounds.forEach((round, index) => {
- console.log(` ${index + 1}. ${round.date.toDateString()}: ${round.rating} (${round.competition})`);
+ debugLog.push(` ${index + 1}. ${round.date.toDateString()}: ${round.rating} (${round.competition})`);
});
let workingRounds = [...eligibleRounds];
@@ -1306,13 +1309,13 @@ function calculatePredictedRating(roundRatings) {
// PDGA Rule: Apply outlier exclusion if ā„7 rounds
if (workingRatings.length >= 7) {
- console.log(`\nš OUTLIER EXCLUSION (ā„7 rounds available):`);
+ debugLog.push('š OUTLIER EXCLUSION (ā„7 rounds available):');
const mean = workingRatings.reduce((sum, r) => sum + r, 0) / workingRatings.length;
const stdDev = calculateStandardDeviation(workingRatings);
- console.log(` Mean: ${mean.toFixed(1)}`);
- console.log(` Std Dev: ${stdDev.toFixed(1)}`);
+ debugLog.push(` Mean: ${mean.toFixed(1)}`);
+ debugLog.push(` Std Dev: ${stdDev.toFixed(1)}`);
// Two PDGA exclusion rules:
// 1. More than 2.5 standard deviations below average
@@ -1320,8 +1323,8 @@ function calculatePredictedRating(roundRatings) {
// 2. More than 100 points below average
const hundredPointCutoff = mean - 100;
- console.log(` 2.5Ļ cutoff: ${stdDevCutoff.toFixed(1)}`);
- console.log(` 100-point cutoff: ${hundredPointCutoff.toFixed(1)}`);
+ debugLog.push(` 2.5Ļ cutoff: ${stdDevCutoff.toFixed(1)}`);
+ debugLog.push(` 100-point cutoff: ${hundredPointCutoff.toFixed(1)}`);
const filteredByStdDev = workingRatings.filter(rating => rating >= stdDevCutoff);
const filteredBy100Points = workingRatings.filter(rating => rating >= hundredPointCutoff);
@@ -1335,23 +1338,23 @@ function calculatePredictedRating(roundRatings) {
const hundredPointOutliers = workingRatings.filter(rating => rating < hundredPointCutoff && rating >= stdDevCutoff);
if (stdDevOutliers.length > 0) {
- console.log(` ā 2.5Ļ outliers removed: ${stdDevOutliers.length} rounds`);
+ debugLog.push(` ā 2.5Ļ outliers removed: ${stdDevOutliers.length} rounds`);
stdDevOutliers.forEach(rating => {
const round = workingRounds.find(r => r.rating === rating);
- console.log(` - ${rating} (${round.date.toDateString()}: ${round.competition})`);
+ debugLog.push(` - ${rating} (${round.date.toDateString()}: ${round.competition})`);
});
}
if (hundredPointOutliers.length > 0) {
- console.log(` ā 100-point outliers removed: ${hundredPointOutliers.length} rounds`);
+ debugLog.push(` ā 100-point outliers removed: ${hundredPointOutliers.length} rounds`);
hundredPointOutliers.forEach(rating => {
const round = workingRounds.find(r => r.rating === rating);
- console.log(` - ${rating} (${round.date.toDateString()}: ${round.competition})`);
+ debugLog.push(` - ${rating} (${round.date.toDateString()}: ${round.competition})`);
});
}
if (stdDevOutliers.length === 0 && hundredPointOutliers.length === 0) {
- console.log(` ā
No outliers detected`);
+ debugLog.push(` ā
No outliers detected`);
}
// Keep filtered rounds only if we still have enough data
@@ -1360,21 +1363,21 @@ function calculatePredictedRating(roundRatings) {
round.rating >= stdDevCutoff && round.rating >= hundredPointCutoff
);
workingRatings = filteredRatings;
- console.log(` ā
Using ${filteredRatings.length} rounds after outlier removal`);
+ debugLog.push(` ā
Using ${filteredRatings.length} rounds after outlier removal`);
} else {
- console.log(` ā ļø Too few rounds after outlier removal (${filteredRatings.length}), keeping all rounds`);
+ debugLog.push(` ā ļø Too few rounds after outlier removal (${filteredRatings.length}), keeping all rounds`);
}
} else {
- console.log(`\nāļø OUTLIER EXCLUSION SKIPPED (only ${workingRatings.length} rounds, need ā„7)`);
+ debugLog.push(`āļø OUTLIER EXCLUSION SKIPPED (only ${workingRatings.length} rounds, need ā„7)`);
}
// PDGA Rule: Most recent 25% of rounds get double weight if ā„9 rounds
- console.log(`\nāļø WEIGHTING (Most recent 25% count double if ā„9 rounds):`);
+ debugLog.push('āļø WEIGHTING (Most recent 25% count double if ā„9 rounds):');
const weightedRatings = [];
if (workingRatings.length >= 9) {
const recentCount = Math.round(workingRatings.length * 0.25);
- console.log(` ā
Double-weighting most recent ${recentCount} rounds`);
+ debugLog.push(` ā
Double-weighting most recent ${recentCount} rounds`);
// Add all ratings once
weightedRatings.push(...workingRatings);
@@ -1383,12 +1386,12 @@ function calculatePredictedRating(roundRatings) {
for (let i = 0; i < recentCount; i++) {
weightedRatings.push(workingRatings[i]);
const round = workingRounds[i];
- console.log(` 2x weight: ${workingRatings[i]} (${round.date.toDateString()}: ${round.competition})`);
+ debugLog.push(` 2x weight: ${workingRatings[i]} (${round.date.toDateString()}: ${round.competition})`);
}
- console.log(` š Total values: ${workingRatings.length} + ${recentCount} double-weighted = ${weightedRatings.length}`);
+ debugLog.push(` š Total values: ${workingRatings.length} + ${recentCount} double-weighted = ${weightedRatings.length}`);
} else {
- console.log(` ā”ļø No double weighting (${workingRatings.length} rounds, need ā„9)`);
+ debugLog.push(` ā”ļø No double weighting (${workingRatings.length} rounds, need ā„9)`);
weightedRatings.push(...workingRatings);
}
@@ -1397,14 +1400,14 @@ function calculatePredictedRating(roundRatings) {
const average = sum / weightedRatings.length;
const finalRating = Math.round(average);
- console.log(`\nšÆ FINAL CALCULATION:`);
- console.log(` Sum: ${sum}`);
- console.log(` Count: ${weightedRatings.length}`);
- console.log(` Average: ${average.toFixed(1)}`);
- console.log(` Final Rating: ${finalRating}`);
- console.log('=== END PDGA CALCULATION ===\n');
+ debugLog.push('šÆ FINAL CALCULATION:');
+ debugLog.push(` Sum: ${sum}`);
+ debugLog.push(` Count: ${weightedRatings.length}`);
+ debugLog.push(` Average: ${average.toFixed(1)}`);
+ debugLog.push(` Final Rating: ${finalRating}`);
+ debugLog.push('=== END PDGA CALCULATION ===');
- return finalRating;
+ return { rating: finalRating, debugLog };
}
function calculateStandardDeviation(ratings) {
@@ -2046,7 +2049,7 @@ app.post('/api/refresh-round-history/:pdgaNumber', async (req, res) => {
competition: round.competition_name
}));
- const predictedRating = calculatePredictedRating(roundsForPrediction);
+ const result = calculatePredictedRating(roundsForPrediction);
// Count official vs new rounds
const officialCount = allRounds.filter(r => r.source === 'official').length;
@@ -2054,7 +2057,8 @@ app.post('/api/refresh-round-history/:pdgaNumber', async (req, res) => {
res.json({
success: true,
- predictedRating,
+ predictedRating: result.rating,
+ debugLog: result.debugLog,
totalRounds: roundsForPrediction.length,
officialRounds: officialCount,
newRounds: newCount,
@@ -2187,11 +2191,12 @@ app.post('/api/predicted-rating/:pdgaNumber', async (req, res) => {
competition: round.competition_name
}));
- const predictedRating = calculatePredictedRating(roundRatings);
+ const result = calculatePredictedRating(roundRatings);
res.json({
pdgaNumber: parseInt(pdgaNumber),
- predictedRating
+ predictedRating: result.rating,
+ debugLog: result.debugLog
});
} catch (error) {
console.error('Error calculating predicted rating:', error.message || error);