const { db } = require('../db'); const { getPlayerFromDB, getRoundHistoryFromDB, savePredictedRatingToDB, savePlayerToDB } = require('../models/player'); const { fetchPlayerDataHTTP, parsePlayerData } = require('../scrapers/player-http'); const { calculatePredictedRating } = require('./rating-calculator'); async function getPlayerDataFromDB(pdgaNumber) { try { const cachedPlayer = await getPlayerFromDB(pdgaNumber); if (cachedPlayer) { console.log(`Loading PDGA ${pdgaNumber} from DB (source of truth)`); let predictedRating = cachedPlayer.predicted_rating; let stdDev = cachedPlayer.std_dev; if (!predictedRating || predictedRating === 0) { predictedRating = await getPredictedRatingFromDB(pdgaNumber); const updatedPlayer = await getPlayerFromDB(pdgaNumber); stdDev = updatedPlayer?.std_dev; } return { pdgaNumber: cachedPlayer.pdga_number, name: cachedPlayer.name, rating: cachedPlayer.current_rating, ratingChange: cachedPlayer.rating_change, predictedRating: predictedRating > 0 ? predictedRating : null, stdDev: stdDev > 0 ? stdDev : null }; } return null; } catch (err) { console.error(`Database error for PDGA ${pdgaNumber}:`, err.message); return null; } } async function scrapePDGARating(pdgaNumber, retries = 3) { console.log(`=== Refreshing PDGA ${pdgaNumber} from PDGA website ===`); for (let attempt = 1; attempt <= retries; attempt++) { try { console.log(`Attempt ${attempt}/${retries} for PDGA ${pdgaNumber} (using HTTP)`); const html = await fetchPlayerDataHTTP(pdgaNumber); const result = parsePlayerData(html, pdgaNumber); try { await savePlayerToDB(result); console.log(`Saved PDGA ${pdgaNumber} to database`); } catch (dbErr) { console.error(`Failed to save PDGA ${pdgaNumber} to database:`, dbErr.message); } console.log(`Successfully scraped PDGA ${pdgaNumber} on attempt ${attempt}`); return result; } catch (error) { console.error(`Attempt ${attempt}/${retries} failed for PDGA ${pdgaNumber}:`, error.message); if (attempt === retries) { return { pdgaNumber, name: 'Error', rating: 0, ratingChange: null, predictedRating: null }; } let retryDelay = 2000 * attempt; if (error.rateLimitInfo) { const retryAfter = error.rateLimitInfo.headers['retry-after']; if (retryAfter) { retryDelay = Math.max(retryDelay, (parseInt(retryAfter) + 1) * 1000); console.log(`Using Retry-After header: waiting ${retryDelay/1000}s`); } } if (error.code === 'ECONNRESET') { retryDelay = Math.max(retryDelay, 10000); console.log(`Connection reset detected: waiting ${retryDelay/1000}s`); } await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } async function getPredictedRatingFromDB(pdgaNumber) { try { const roundHistory = await getRoundHistoryFromDB(pdgaNumber); if (roundHistory.length > 0) { console.log(`Using ${roundHistory.length} cached rounds for PDGA ${pdgaNumber} prediction`); const roundRatings = roundHistory.map(round => ({ rating: round.rating, date: new Date(round.date), competition: round.competition_name || 'Unknown' })); const result = calculatePredictedRating(roundRatings); await savePredictedRatingToDB(pdgaNumber, result.rating, result.stdDev); return result.rating; } return 0; } catch (err) { console.error(`Error getting predicted rating from DB for ${pdgaNumber}:`, err.message); return 0; } } async function getAllRatingsFromDB(progressCallback = null) { try { const allPlayers = await new Promise((resolve, reject) => { db.all( 'SELECT pdga_number, name, current_rating, rating_change FROM players ORDER BY pdga_number', [], (err, rows) => { if (err) reject(err); else resolve(rows || []); } ); }); console.log(`Loading ${allPlayers.length} players from database...`); const ratings = []; const total = allPlayers.length; for (let i = 0; i < allPlayers.length; i++) { const player = allPlayers[i]; const pdgaNumber = player.pdga_number; if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'loading' }); } try { const playerData = await getPlayerDataFromDB(pdgaNumber); if (playerData) { ratings.push(playerData); } if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'completed', name: playerData ? playerData.name : player.name }); } } catch (error) { console.error(`Failed to load PDGA ${pdgaNumber} from database:`, error.message); const errorData = { pdgaNumber: parseInt(pdgaNumber), name: player.name || 'Database Error', rating: player.current_rating, ratingChange: player.rating_change, predictedRating: null }; ratings.push(errorData); if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'error', name: player.name || 'Database Error' }); } } } return ratings.sort((a, b) => (b.rating || 0) - (a.rating || 0)); } catch (error) { console.error('Error loading players from database:', error); return []; } } async function refreshAllPlayersInDB(progressCallback = null) { try { const allPlayers = await new Promise((resolve, reject) => { db.all( 'SELECT pdga_number, name FROM players ORDER BY pdga_number', [], (err, rows) => { if (err) reject(err); else resolve(rows || []); } ); }); console.log(`Refreshing ${allPlayers.length} players from database...`); const ratings = []; const total = allPlayers.length; for (let i = 0; i < allPlayers.length; i++) { const player = allPlayers[i]; const pdgaNumber = player.pdga_number; console.log(`Refreshing PDGA ${pdgaNumber}... (${i + 1}/${total})`); if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'loading' }); } try { const playerData = await scrapePDGARating(pdgaNumber); ratings.push(playerData); if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'completed', name: playerData.name }); } await new Promise(resolve => setTimeout(resolve, 2000)); } catch (error) { console.error(`Failed to refresh PDGA ${pdgaNumber}:`, error.message); const errorData = { pdgaNumber: parseInt(pdgaNumber), name: player.name || 'Error', rating: 0, ratingChange: null, predictedRating: null }; ratings.push(errorData); if (progressCallback) { progressCallback({ current: i + 1, total, pdgaNumber, status: 'error', name: player.name || 'Error' }); } } } return ratings.sort((a, b) => (b.rating || 0) - (a.rating || 0)); } catch (error) { console.error('Error refreshing all players:', error); return []; } } module.exports = { getPlayerDataFromDB, scrapePDGARating, getPredictedRatingFromDB, getAllRatingsFromDB, refreshAllPlayersInDB };