Add debug information display for prediction calculations
- Modified calculatePredictedRating function to collect debug logs instead of console output - Added debug modal UI with ? icon next to predicted ratings - Updated API responses to include detailed calculation steps - Fixed compatibility issue with competition property in round data - Users can now see PDGA rules, filtering, outliers, and weighting details 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+120
@@ -253,6 +253,64 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -272,6 +330,15 @@
|
|||||||
<div id="ratings-table"></div>
|
<div id="ratings-table"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Debug Modal -->
|
||||||
|
<div id="debug-modal" class="debug-modal" onclick="closeDebugModal(event)">
|
||||||
|
<div class="debug-content" onclick="event.stopPropagation()">
|
||||||
|
<button class="debug-close" onclick="closeDebugModal()">×</button>
|
||||||
|
<div class="debug-header" id="debug-header">Prediction Calculation Details</div>
|
||||||
|
<div class="debug-log" id="debug-log">Loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function fetchRatingsWithProgress() {
|
function fetchRatingsWithProgress() {
|
||||||
const progressSection = document.getElementById('progress-section');
|
const progressSection = document.getElementById('progress-section');
|
||||||
@@ -373,6 +440,7 @@
|
|||||||
<td class="predicted-rating mobile-hide" id="predicted-${player.pdgaNumber}">
|
<td class="predicted-rating mobile-hide" id="predicted-${player.pdgaNumber}">
|
||||||
<div class="refresh-section">
|
<div class="refresh-section">
|
||||||
${player.predictedRating || 'N/A'}
|
${player.predictedRating || 'N/A'}
|
||||||
|
<i class="fas fa-question-circle debug-icon" onclick="showDebugInfo(${player.pdgaNumber})" title="Show calculation details" style="margin-left: 5px; color: #6c757d; cursor: pointer; opacity: 0.6;"></i>
|
||||||
<i class="fas fa-sync-alt refresh-icon" onclick="refreshRoundHistory(${player.pdgaNumber})" title="Refresh prediction data"></i>
|
<i class="fas fa-sync-alt refresh-icon" onclick="refreshRoundHistory(${player.pdgaNumber})" title="Refresh prediction data"></i>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -664,6 +732,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
// Cache debug info for later use
|
||||||
|
if (data.debugLog) {
|
||||||
|
cachedDebugInfo[pdgaNumber] = data.debugLog;
|
||||||
|
}
|
||||||
|
|
||||||
// Update predicted rating if the element exists
|
// Update predicted rating if the element exists
|
||||||
const predictedCell = document.getElementById(`predicted-${pdgaNumber}`);
|
const predictedCell = document.getElementById(`predicted-${pdgaNumber}`);
|
||||||
if (predictedCell) {
|
if (predictedCell) {
|
||||||
@@ -846,6 +919,53 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug modal functions
|
||||||
|
let cachedDebugInfo = {};
|
||||||
|
|
||||||
|
async function showDebugInfo(pdgaNumber) {
|
||||||
|
const modal = document.getElementById('debug-modal');
|
||||||
|
const header = document.getElementById('debug-header');
|
||||||
|
const log = document.getElementById('debug-log');
|
||||||
|
|
||||||
|
// Get player name for header
|
||||||
|
const playerNameElement = document.querySelector(`#row-${pdgaNumber} .player-name a`);
|
||||||
|
const playerName = playerNameElement ? playerNameElement.textContent : `PDGA #${pdgaNumber}`;
|
||||||
|
|
||||||
|
header.textContent = `Prediction Calculation Details - ${playerName}`;
|
||||||
|
log.textContent = 'Loading calculation details...';
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if we have cached debug info
|
||||||
|
if (cachedDebugInfo[pdgaNumber]) {
|
||||||
|
log.textContent = cachedDebugInfo[pdgaNumber].join('\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get debug info from the latest refresh call
|
||||||
|
const response = await fetch(`/api/refresh-round-history/${pdgaNumber}`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.debugLog) {
|
||||||
|
cachedDebugInfo[pdgaNumber] = data.debugLog;
|
||||||
|
log.textContent = data.debugLog.join('\n');
|
||||||
|
} else {
|
||||||
|
log.textContent = 'No debug information available. Try refreshing the prediction first.';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching debug info:', error);
|
||||||
|
log.textContent = 'Error loading debug information. Please try again.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDebugModal(event) {
|
||||||
|
const modal = document.getElementById('debug-modal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
fetchRatingsWithProgress();
|
fetchRatingsWithProgress();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -548,10 +548,10 @@ async function getPredictedRating(browser, pdgaNumber, retries = 2) {
|
|||||||
try {
|
try {
|
||||||
console.log(`Predicted rating attempt ${attempt}/${retries} for PDGA ${pdgaNumber}`);
|
console.log(`Predicted rating attempt ${attempt}/${retries} for PDGA ${pdgaNumber}`);
|
||||||
const roundRatings = await getPlayerCompetitionRatings(browser, pdgaNumber);
|
const roundRatings = await getPlayerCompetitionRatings(browser, pdgaNumber);
|
||||||
const predictedRating = calculatePredictedRating(roundRatings);
|
const result = calculatePredictedRating(roundRatings);
|
||||||
|
|
||||||
if (predictedRating > 0) {
|
if (result.rating > 0) {
|
||||||
return predictedRating;
|
return result.rating;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempt < retries) {
|
if (attempt < retries) {
|
||||||
@@ -579,10 +579,12 @@ async function getPredictedRatingFromDB(pdgaNumber) {
|
|||||||
// Convert to the format expected by calculatePredictedRating
|
// Convert to the format expected by calculatePredictedRating
|
||||||
const roundRatings = roundHistory.map(round => ({
|
const roundRatings = roundHistory.map(round => ({
|
||||||
rating: round.rating,
|
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;
|
return 0;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1242,19 +1244,20 @@ function getNextPDGAUpdateDate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function calculatePredictedRating(roundRatings) {
|
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) {
|
if (!roundRatings || roundRatings.length === 0) {
|
||||||
console.log('❌ No rounds provided for prediction');
|
debugLog.push('❌ No rounds provided for prediction');
|
||||||
return 0;
|
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
|
// PDGA Simulation: Only include rounds that would be rated by next update
|
||||||
const nextUpdateDate = getNextPDGAUpdateDate();
|
const nextUpdateDate = getNextPDGAUpdateDate();
|
||||||
console.log(`🎯 PDGA Update Simulation: Next update date is ${nextUpdateDate.toDateString()}`);
|
debugLog.push(`🎯 PDGA Update Simulation: Next update date is ${nextUpdateDate.toDateString()}`);
|
||||||
console.log(` Only including rounds played before ${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
|
// Sort all rounds by date (most recent first), but only include rounds before next update
|
||||||
const allSortedRounds = roundRatings
|
const allSortedRounds = roundRatings
|
||||||
@@ -1262,25 +1265,25 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
.sort((a, b) => b.date - a.date);
|
.sort((a, b) => b.date - a.date);
|
||||||
|
|
||||||
if (allSortedRounds.length === 0) {
|
if (allSortedRounds.length === 0) {
|
||||||
console.log('❌ No valid rounds after filtering for update date');
|
debugLog.push('❌ No valid rounds after filtering for update date');
|
||||||
return 0;
|
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
|
// PDGA Rule: Use rounds from 12 months prior to next update date
|
||||||
const twelveMonthsBeforeUpdate = new Date(nextUpdateDate);
|
const twelveMonthsBeforeUpdate = new Date(nextUpdateDate);
|
||||||
twelveMonthsBeforeUpdate.setFullYear(twelveMonthsBeforeUpdate.getFullYear() - 1);
|
twelveMonthsBeforeUpdate.setFullYear(twelveMonthsBeforeUpdate.getFullYear() - 1);
|
||||||
|
|
||||||
const mostRecentDate = allSortedRounds[0].date;
|
const mostRecentDate = allSortedRounds[0].date;
|
||||||
console.log(`📅 Most recent round: ${mostRecentDate.toDateString()}`);
|
debugLog.push(`📅 Most recent round: ${mostRecentDate.toDateString()}`);
|
||||||
console.log(`📅 12-month cutoff: ${twelveMonthsBeforeUpdate.toDateString()} (1 year before update)`);
|
debugLog.push(`📅 12-month cutoff: ${twelveMonthsBeforeUpdate.toDateString()} (1 year before update)`);
|
||||||
|
|
||||||
// Step 1: Get rounds from last 12 months before update
|
// Step 1: Get rounds from last 12 months before update
|
||||||
let eligibleRounds = allSortedRounds.filter(r => r.date >= twelveMonthsBeforeUpdate);
|
let eligibleRounds = allSortedRounds.filter(r => r.date >= twelveMonthsBeforeUpdate);
|
||||||
|
|
||||||
console.log(`\n🗓️ 12-MONTH FILTERING:`);
|
debugLog.push('🗓️ 12-MONTH FILTERING:');
|
||||||
console.log(`✅ Rounds in last 12 months: ${eligibleRounds.length}`);
|
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
|
// PDGA Rule: If fewer than 8 rounds in 12 months, extend to 24 months before update
|
||||||
if (eligibleRounds.length < 8) {
|
if (eligibleRounds.length < 8) {
|
||||||
@@ -1288,17 +1291,17 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
twentyFourMonthsBeforeUpdate.setFullYear(twentyFourMonthsBeforeUpdate.getFullYear() - 2);
|
twentyFourMonthsBeforeUpdate.setFullYear(twentyFourMonthsBeforeUpdate.getFullYear() - 2);
|
||||||
|
|
||||||
eligibleRounds = allSortedRounds.filter(r => r.date >= twentyFourMonthsBeforeUpdate);
|
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) {
|
if (eligibleRounds.length === 0) {
|
||||||
console.log('❌ No eligible rounds found');
|
debugLog.push('❌ No eligible rounds found');
|
||||||
return 0;
|
return { rating: 0, debugLog };
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n📈 ELIGIBLE ROUNDS: ${eligibleRounds.length}`);
|
debugLog.push(`📈 ELIGIBLE ROUNDS: ${eligibleRounds.length}`);
|
||||||
eligibleRounds.forEach((round, index) => {
|
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];
|
let workingRounds = [...eligibleRounds];
|
||||||
@@ -1306,13 +1309,13 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
|
|
||||||
// PDGA Rule: Apply outlier exclusion if ≥7 rounds
|
// PDGA Rule: Apply outlier exclusion if ≥7 rounds
|
||||||
if (workingRatings.length >= 7) {
|
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 mean = workingRatings.reduce((sum, r) => sum + r, 0) / workingRatings.length;
|
||||||
const stdDev = calculateStandardDeviation(workingRatings);
|
const stdDev = calculateStandardDeviation(workingRatings);
|
||||||
|
|
||||||
console.log(` Mean: ${mean.toFixed(1)}`);
|
debugLog.push(` Mean: ${mean.toFixed(1)}`);
|
||||||
console.log(` Std Dev: ${stdDev.toFixed(1)}`);
|
debugLog.push(` Std Dev: ${stdDev.toFixed(1)}`);
|
||||||
|
|
||||||
// Two PDGA exclusion rules:
|
// Two PDGA exclusion rules:
|
||||||
// 1. More than 2.5 standard deviations below average
|
// 1. More than 2.5 standard deviations below average
|
||||||
@@ -1320,8 +1323,8 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
// 2. More than 100 points below average
|
// 2. More than 100 points below average
|
||||||
const hundredPointCutoff = mean - 100;
|
const hundredPointCutoff = mean - 100;
|
||||||
|
|
||||||
console.log(` 2.5σ cutoff: ${stdDevCutoff.toFixed(1)}`);
|
debugLog.push(` 2.5σ cutoff: ${stdDevCutoff.toFixed(1)}`);
|
||||||
console.log(` 100-point cutoff: ${hundredPointCutoff.toFixed(1)}`);
|
debugLog.push(` 100-point cutoff: ${hundredPointCutoff.toFixed(1)}`);
|
||||||
|
|
||||||
const filteredByStdDev = workingRatings.filter(rating => rating >= stdDevCutoff);
|
const filteredByStdDev = workingRatings.filter(rating => rating >= stdDevCutoff);
|
||||||
const filteredBy100Points = workingRatings.filter(rating => rating >= hundredPointCutoff);
|
const filteredBy100Points = workingRatings.filter(rating => rating >= hundredPointCutoff);
|
||||||
@@ -1335,23 +1338,23 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
const hundredPointOutliers = workingRatings.filter(rating => rating < hundredPointCutoff && rating >= stdDevCutoff);
|
const hundredPointOutliers = workingRatings.filter(rating => rating < hundredPointCutoff && rating >= stdDevCutoff);
|
||||||
|
|
||||||
if (stdDevOutliers.length > 0) {
|
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 => {
|
stdDevOutliers.forEach(rating => {
|
||||||
const round = workingRounds.find(r => r.rating === 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) {
|
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 => {
|
hundredPointOutliers.forEach(rating => {
|
||||||
const round = workingRounds.find(r => r.rating === 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) {
|
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
|
// Keep filtered rounds only if we still have enough data
|
||||||
@@ -1360,21 +1363,21 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
round.rating >= stdDevCutoff && round.rating >= hundredPointCutoff
|
round.rating >= stdDevCutoff && round.rating >= hundredPointCutoff
|
||||||
);
|
);
|
||||||
workingRatings = filteredRatings;
|
workingRatings = filteredRatings;
|
||||||
console.log(` ✅ Using ${filteredRatings.length} rounds after outlier removal`);
|
debugLog.push(` ✅ Using ${filteredRatings.length} rounds after outlier removal`);
|
||||||
} else {
|
} 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 {
|
} 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
|
// 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 = [];
|
const weightedRatings = [];
|
||||||
|
|
||||||
if (workingRatings.length >= 9) {
|
if (workingRatings.length >= 9) {
|
||||||
const recentCount = Math.round(workingRatings.length * 0.25);
|
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
|
// Add all ratings once
|
||||||
weightedRatings.push(...workingRatings);
|
weightedRatings.push(...workingRatings);
|
||||||
@@ -1383,12 +1386,12 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
for (let i = 0; i < recentCount; i++) {
|
for (let i = 0; i < recentCount; i++) {
|
||||||
weightedRatings.push(workingRatings[i]);
|
weightedRatings.push(workingRatings[i]);
|
||||||
const round = workingRounds[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 {
|
} else {
|
||||||
console.log(` ➡️ No double weighting (${workingRatings.length} rounds, need ≥9)`);
|
debugLog.push(` ➡️ No double weighting (${workingRatings.length} rounds, need ≥9)`);
|
||||||
weightedRatings.push(...workingRatings);
|
weightedRatings.push(...workingRatings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,14 +1400,14 @@ function calculatePredictedRating(roundRatings) {
|
|||||||
const average = sum / weightedRatings.length;
|
const average = sum / weightedRatings.length;
|
||||||
const finalRating = Math.round(average);
|
const finalRating = Math.round(average);
|
||||||
|
|
||||||
console.log(`\n🎯 FINAL CALCULATION:`);
|
debugLog.push('🎯 FINAL CALCULATION:');
|
||||||
console.log(` Sum: ${sum}`);
|
debugLog.push(` Sum: ${sum}`);
|
||||||
console.log(` Count: ${weightedRatings.length}`);
|
debugLog.push(` Count: ${weightedRatings.length}`);
|
||||||
console.log(` Average: ${average.toFixed(1)}`);
|
debugLog.push(` Average: ${average.toFixed(1)}`);
|
||||||
console.log(` Final Rating: ${finalRating}`);
|
debugLog.push(` Final Rating: ${finalRating}`);
|
||||||
console.log('=== END PDGA CALCULATION ===\n');
|
debugLog.push('=== END PDGA CALCULATION ===');
|
||||||
|
|
||||||
return finalRating;
|
return { rating: finalRating, debugLog };
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateStandardDeviation(ratings) {
|
function calculateStandardDeviation(ratings) {
|
||||||
@@ -2046,7 +2049,7 @@ app.post('/api/refresh-round-history/:pdgaNumber', async (req, res) => {
|
|||||||
competition: round.competition_name
|
competition: round.competition_name
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const predictedRating = calculatePredictedRating(roundsForPrediction);
|
const result = calculatePredictedRating(roundsForPrediction);
|
||||||
|
|
||||||
// Count official vs new rounds
|
// Count official vs new rounds
|
||||||
const officialCount = allRounds.filter(r => r.source === 'official').length;
|
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({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
predictedRating,
|
predictedRating: result.rating,
|
||||||
|
debugLog: result.debugLog,
|
||||||
totalRounds: roundsForPrediction.length,
|
totalRounds: roundsForPrediction.length,
|
||||||
officialRounds: officialCount,
|
officialRounds: officialCount,
|
||||||
newRounds: newCount,
|
newRounds: newCount,
|
||||||
@@ -2187,11 +2191,12 @@ app.post('/api/predicted-rating/:pdgaNumber', async (req, res) => {
|
|||||||
competition: round.competition_name
|
competition: round.competition_name
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const predictedRating = calculatePredictedRating(roundRatings);
|
const result = calculatePredictedRating(roundRatings);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
pdgaNumber: parseInt(pdgaNumber),
|
pdgaNumber: parseInt(pdgaNumber),
|
||||||
predictedRating
|
predictedRating: result.rating,
|
||||||
|
debugLog: result.debugLog
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error calculating predicted rating:', error.message || error);
|
console.error('Error calculating predicted rating:', error.message || error);
|
||||||
|
|||||||
Reference in New Issue
Block a user