### Get All Players (ATP and WTA) Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Retrieves a list of all ATP and WTA players with their rankings and biographical data. Supports filtering by tour using the 'tour' query parameter. ```bash # Get all players (both tours) curl -X GET "http://localhost:5002/api/players" ``` ```json { "ATP": [ {"rank": 1, "name": "Jannik Sinner", "tour": "ATP", "country": "ITA", "birthdate": "2001-08-16", "age": 23.5}, {"rank": 2, "name": "Carlos Alcaraz", "tour": "ATP", "country": "ESP", "birthdate": "2003-05-05", "age": 21.8} ], "WTA": [ {"rank": 1, "name": "Iga Swiatek", "tour": "WTA", "country": "POL", "birthdate": "2001-05-31", "age": 23.7}, {"rank": 2, "name": "Aryna Sabalenka", "tour": "WTA", "country": "BLR", "birthdate": "1998-05-05", "age": 26.8} ] } ``` ```bash # Filter by tour curl -X GET "http://localhost:5002/api/players?tour=ATP" ``` ```json { "players": [ {"rank": 1, "name": "Jannik Sinner", "tour": "ATP", "country": "ITA", "birthdate": "2001-08-16", "age": 23.5} ] } ``` -------------------------------- ### Get Surface-Specific Career Stats Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Filters match data by surface type before calculating career statistics. ```python hard_matches = matches[matches['surf'] == 'Hard'] hard_career = calculate_career_stats(hard_matches) print(hard_career.loc['career', ['W-L', 'win%']]) ``` -------------------------------- ### Get All Players Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Retrieves ATP and WTA player rankings with biographical data. Supports filtering by tour. ```APIDOC ## GET /api/players ### Description Retrieves ATP and WTA player rankings with biographical data including rank, country, birthdate, and age. ### Method GET ### Endpoint /api/players #### Query Parameters - **tour** (string) - Optional - Filters players by tour (e.g., 'ATP', 'WTA'). ### Response #### Success Response (200) - **ATP** (array) - List of ATP players with their stats. - **WTA** (array) - List of WTA players with their stats. - **players** (array) - List of players when filtered by tour. ### Request Example ```bash # Get all players (both tours) curl -X GET "http://localhost:5002/api/players" # Filter by tour curl -X GET "http://localhost:5002/api/players?tour=ATP" ``` ### Response Example ```json { "ATP": [ {"rank": 1, "name": "Jannik Sinner", "tour": "ATP", "country": "ITA", "birthdate": "2001-08-16", "age": 23.5}, {"rank": 2, "name": "Carlos Alcaraz", "tour": "ATP", "country": "ESP", "birthdate": "2003-05-05", "age": 21.8} ], "WTA": [ {"rank": 1, "name": "Iga Swiatek", "tour": "WTA", "country": "POL", "birthdate": "2001-05-31", "age": 23.7}, {"rank": 2, "name": "Aryna Sabalenka", "tour": "WTA", "country": "BLR", "birthdate": "1998-05-05", "age": 26.8} ] } ``` ``` -------------------------------- ### Initialize Head-to-Head Charts Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/h2h.html Creates line and bar charts using Chart.js to display player performance metrics. Requires a valid DOM element context and pre-processed data objects. ```javascript y(); }); // Cumulative wins chart h2hProgressChart = new Chart(document.getElementById('h2hProgress').getContext('2d'), { type: 'line', data: { labels, datasets: [{ label: p1, data: s1, borderColor: 'rgb(30,58,138)', backgroundColor: 'rgba(30,58,138,.1)', tension: .25 }, { label: p2, data: s2, borderColor: 'rgb(239,68,68)', backgroundColor: 'rgba(239,68,68,.1)', tension: .25 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 10 } } }, scales: { x: { display: false }, y: { beginAtZero: true, ticks: { precision: 0 } } } } }); // Wins by surface chart const surfaces = ['Hard','Clay','Grass','Carpet']; const b = response.summary.surface_breakdown || {}; h2hSurfacesChart = new Chart(document.getElementById('h2hSurfaces').getContext('2d'), { type: 'bar', data: { labels: surfaces, datasets: [{ label: p1, data: surfaces.map(s => (b[s] && b[s][p1]) ? b[s][p1] : 0), backgroundColor: 'rgba(30,58,138,.8)' }, { label: p2, data: surfaces.map(s => (b[s] && b[s][p2]) ? b[s][p2] : 0), backgroundColor: 'rgba(16,185,129,.8)' }]}, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { stepSize: 1, precision: 0 } } }, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 10 } } } } }); // Wins by round chart h2hRoundsChart = new Chart(document.getElementById('h2hRounds').getContext('2d'), { type: 'bar', data: { labels: roundLabelsArray, datasets: [{ label: p1, data: p1RoundWins, backgroundColor: 'rgba(30,58,138,.8)' }, { label: p2, data: p2RoundWins, backgroundColor: 'rgba(245,158,11,.8)' }]}, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', scales: { x: { beginAtZero: true, ticks: { stepSize: 1, precision: 0 } } }, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 10 } } } } }); }, error: function(xhr) { $('.loading').hide(); let errorMsg = xhr.responseJSON ? xhr.responseJSON.error : 'An error occurred'; $('.error-message').text(errorMsg).show(); } }); }); {% endblock %} ``` -------------------------------- ### Render Performance Chart Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/compare.html Initializes a Chart.js bar chart to visualize win, hold, and break percentages for two players. ```javascript // --- Performance Chart (MODIFIED) --- const winPct = getStat(statsData, 'win%'); const hold = getStat(statsData, 'hold%'); const brk = getStat(statsData, 'break%'); let chartHtml = `
Performance Trend
`; $('#results').html(surfaceIndicator + overviewHtml + detailsHtml + chartHtml); if (performanceChart) performanceChart.destroy(); performanceChart = new Chart(document.getElementById('performanceChart').getContext('2d'), { type: 'bar', data: { labels: ['Win %', 'Hold %', 'Break %'], datasets: [{ label: player1, data: [winPct.p1, hold.p1, brk.p1], backgroundColor: 'rgba(30,58,138,.7)', borderColor: 'rgb(30,58,138)', borderWidth: 1 }, { label: player2, data: [winPct.p2, hold.p2, brk.p2], backgroundColor: 'rgba(16,185,129,.7)', borderColor: 'rgb(16,185,129)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: 'Percentage (%)' } } }, plugins: { legend: { position: 'top' }, tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)}%` } } } } }); } ``` -------------------------------- ### Initialize DataTables for Player Tables Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/players.html Initializes DataTables for both ATP and WTA player tables. Configures pagination, sorting, column definitions, and language settings for an interactive experience. ```javascript function initPlayersTable(sel){ $(sel).DataTable({ pageLength: 18, order: [[0, 'asc']], // Sort by rank ascending lengthChange: false, autoWidth: false, columnDefs: [ { targets: [0, 3], type: 'num', className: 'dt-body-right' }, // Rank & Age numeric/right-aligned { targets: [2], className: 'dt-body-center' } // Nat center ], language: { search: 'Filter:' }, // Each table maintains its own state independently stateSave: false, // Ensure tables don't interfere with each other drawCallback: function() { // No cross-table synchronization } }); } // Initialize tables independently initPlayersTable('#atpTable'); initPlayersTable('#wtaTable'); ``` -------------------------------- ### Render WTA Player List in Jinja2 Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/players.html Iterates through the WTA player list to display rank, name, country, and age. Uses Jinja2 templating for dynamic content generation. ```html {% for p in wta_players %} {% endfor %} Rank Player Nat Age {{ p.rank }} [{{ p.name }}]({{ url_for('career_stats', player=p.name) }}) {{ p.country or '-' }} {% if p.age is not none %} {{ "%.1f"|format(p.age) }} {% else %} - {% endif %} ``` -------------------------------- ### Player Autocomplete Initialization Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/base.html Initializes player name autocomplete for input fields with IDs 'player', 'player1', and 'player2'. Requires jQuery and a global PLAYER_SUGGEST_URL. ```javascript window.PLAYER_SUGGEST_URL = "{{ url_for('player_suggest') }}"; function attachAutocomplete(selector, options) { var $el = $(selector); if (!$el.length || typeof $el.autocomplete !== 'function') return; var minLen = (options && options.minLength) || 2; var limit = (options && options.limit) || 10; var tour = (options && options.tour) || null; $el.autocomplete({ minLength: minLen, delay: 150, source: function(request, response) { var query = { q: request.term, limit: limit }; if (tour) query.tour = tour; $.getJSON(window.PLAYER_SUGGEST_URL, query, function(data) { var items = (data && data.suggestions) ? data.suggestions : []; response(items.map(function(it) { if (typeof it === 'string') return { label: it, value: it }; return { label: it.label || it.name, value: it.value || it.name }; })); }); } }); } $(function(){ if ($('#player').length) attachAutocomplete('#player'); if ($('#player1').length) attachAutocomplete('#player1'); if ($('#player2').length) attachAutocomplete('#player2'); }); ``` -------------------------------- ### Fetch Player Matches with Python Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Retrieves match data for a player, utilizing local SQLite caching. The force_refresh parameter can be used to bypass the cache. ```python from mod import get_player_matches # Fetch player matches (uses cache if available) matches_df = get_player_matches("Roger Federer") # Force refresh from web source matches_df = get_player_matches("Roger Federer", force_refresh=True) # DataFrame columns: date, tourn, surf, level, wl, round, score, opp, orank, # aces, dfs, pts, firsts, fwon, swon, games, saved, chances, # oaces, odfs, opts, ofirsts, ofwon, oswon, ogames, osaved, ochances, # tb_won, tb_lost print(matches_df[['date', 'tourn', 'opp', 'wl', 'score']].head()) # date tourn opp wl score # 0 2024-06-24 Wimbledon Matteo Berrettini W 6-3 6-4 6-2 # 1 2024-06-22 Wimbledon Denis Shapovalov W 7-5 7-5 6-4 ``` -------------------------------- ### Cache and Database Configuration Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Sets expiration times for cache data and defines the database file path. These parameters control data freshness and storage location. ```python # Cache Configuration CACHE_EXPIRE_HOURS = 6 PLAYER_LIST_CACHE_DAYS = 7 # Database Configuration DATABASE_PATH = 'tennis_stats.db' ``` -------------------------------- ### Generate Match Statistics HTML Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/compare.html Constructs the HTML structure for match statistics, including win percentages and average opponent rank, while applying conditional styling based on performance. ```javascript 1_value ? `${tbWL.player1_value} (${row.player1_value || 'N/A'}%)` : (row.player1_value ? `${row.player1_value}%` : 'N/A'); p2Display = tbWL && tbWL.player2_value ? `${tbWL.player2_value} (${row.player2_value || 'N/A'}%)` : (row.player2_value ? `${row.player2_value}%` : 'N/A'); detailsHtml += `
Tiebreak
${p1Display}vs${p2Display}
`; } else { detailsHtml += `
${formatStatName(stat)}
${row.player1_value || 'N/A'}%vs${row.player2_value || 'N/A'}%
`; } } }); detailsHtml += ``; // --- Match Stats --- detailsHtml += `
Match Statistics
`; const matchStats = ['top5_win%', 'top10_win%', 'top20_win%', 'top50_win%', 'top100_win%', 'avg_opp_rank']; matchStats.forEach(stat => { const row = statsData.find(s => s.stat === stat); if (row) { const p1Better = isBetterValue(stat, row.player1_value, row.player2_value); let p1Display, p2Display; if (stat.startsWith('top') && stat.endsWith('_win%')) { const wlRow = statsData.find(s => s.stat === stat.replace('_win%', '_W-L')); p1Display = wlRow && wlRow.player1_value ? `${wlRow.player1_value} (${row.player1_value || 'N/A'}%)` : (row.player1_value ? `${row.player1_value}%` : 'N/A'); p2Display = wlRow && wlRow.player2_value ? `${wlRow.player2_value} (${row.player2_value || 'N/A'}%)` : (row.player2_value ? `${row.player2_value}%` : 'N/A'); } else { // For avg_opp_rank p1Display = row.player1_value || 'N/A'; p2Display = row.player2_value || 'N/A'; } detailsHtml += `
${formatStatName(stat)}
${p1Display}vs${p2Display}
`; } }); detailsHtml += `
`; ``` -------------------------------- ### Calculate Career Statistics with Python Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Computes lifetime aggregated statistics for a player based on their match history. ```python from mod import get_player_matches, calculate_career_stats matches = get_player_matches("Serena Williams") career = calculate_career_stats(matches) print(career.loc['career', ['W-L', 'win%', 'tb_W-L', 'tb_win%', 'top5_W-L', 'top5_win%']]) # W-L 857-153 # win% 84.8 # tb_W-L 142-98 # tb_win% 59.2 # top5_W-L 98-45 # top5_win% 68.5 ``` -------------------------------- ### Access Player Directory Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Retrieves ranked player lists and provides autocomplete suggestions for player names. ```python from mod import get_players, suggest_players # Get all ranked players all_players = get_players() print(f"ATP players: {len(all_players['ATP'])}, WTA players: {len(all_players['WTA'])}") # Get specific tour atp_players = get_players(tour='ATP') print(f"Top 5 ATP: {[p['name'] for p in atp_players[:5]]}") # Force refresh from web fresh_players = get_players(tour='WTA', force_refresh=True) # Autocomplete suggestions suggestions = suggest_players("nad", limit=5) print(suggestions) # [{'label': 'Rafael Nadal', 'value': 'Rafael Nadal', 'tour': 'ATP'}] # Filter by tour wta_suggestions = suggest_players("sab", limit=3, tour='WTA') print(wta_suggestions) # [{'label': 'Aryna Sabalenka', 'value': 'Aryna Sabalenka', 'tour': 'WTA'}] ``` -------------------------------- ### Render Match Statistics HTML Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/career.html Generates the HTML structure for displaying match statistics like hold percentage and opponent rank comparisons. ```javascript class="stat-name">Hold %
${displayData['hold%'] || 'N/A'}%
Break Points Saved %
${displayData['bp_saved%'] || 'N/A'}%
Break %
${displayData['break%'] || 'N/A'}%
Tiebreak
${displayData['tb_W-L'] ? `${displayData['tb_W-L']} (${displayData['tb_win%'] || 'N/A'}%)` : displayData['tb_win%'] ? `${displayData['tb_win%']}%` : 'N/A'}
Match Statistics
vs Top 5
${displayData['top5_W-L'] ? `${displayData['top5_W-L']} (${displayData['top5_win%'] || 'N/A'}%)` : (displayData['top5_win%'] ? `${displayData['top5_win%']}%` : 'N/A')}
vs Top 10
${displayData['top10_W-L'] ? `${displayData['top10_W-L']} (${displayData['top10_win%'] || 'N/A'}%)` : (displayData['top10_win%'] ? `${displayData['top10_win%']}%` : 'N/A')}
vs Top 20
${displayData['top20_W-L'] ? `${displayData['top20_W-L']} (${displayData['top20_win%'] || 'N/A'}%)` : (displayData['top20_win%'] ? `${displayData['top20_win%']}%` : 'N/A')}
vs Top 50
${displayData['top50_W-L'] ? `${displayData['top50_W-L']} (${displayData['top50_win%'] || 'N/A'}%)` : (displayData['top50_win%'] ? `${displayData['top50_win%']}%` : 'N/A')}
vs Top 100
${displayData['top100_W-L'] ? `${displayData['top100_W-L']} (${displayData['top100_win%'] || 'N/A'}%)` : (displayData['top100_win%'] ? `${displayData['top100_win%']}%` : 'N/A')}
Avg Opponent Rank
${displayData['avg_opp_rank'] || 'N/A'}
`; $('#yearStats').html(statsHtml); updateTrendChart(surface); ``` -------------------------------- ### Retrieve Career Statistics via REST API Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Fetches comprehensive career statistics for a specified player using a POST request. ```bash # Get career statistics for a player curl -X POST "http://localhost:5002/career" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "player=Iga Swiatek" ``` -------------------------------- ### Player Autocomplete Search Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Provides autocomplete suggestions for player names. Accepts a query parameter 'q' for the search term and an optional 'tour' parameter to filter results. The 'limit' parameter controls the number of suggestions returned. ```bash # Search for players matching query curl -X GET "http://localhost:5002/api/player-suggest?q=djok&limit=5" ``` ```json { "suggestions": [ {"label": "Novak Djokovic", "value": "Novak Djokovic", "tour": "ATP"} ] } ``` ```bash # Filter by tour curl -X GET "http://localhost:5002/api/player-suggest?q=iga&tour=WTA&limit=3" ``` ```json { "suggestions": [ {"label": "Iga Swiatek", "value": "Iga Swiatek", "tour": "WTA"} ] } ``` -------------------------------- ### Render ATP Player List in Jinja2 Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/players.html Iterates through the ATP player list to display rank, name, country, and age. Uses Jinja2 templating for dynamic content generation. ```html {% for p in atp_players %} {% endfor %} Rank Player Nat Age {{ p.rank }} [{{ p.name }}]({{ url_for('career_stats', player=p.name) }}) {{ p.country or '-' }} {% if p.age is not none %} {{ "%.1f"|format(p.age) }} {% else %} - {% endif %} ``` -------------------------------- ### API and Request Configuration Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Configures API base URLs, request delays, and retry attempts. Adjust these settings to manage API interaction and prevent rate limiting. ```python # API Configuration BASE_URL = 'https://www.tennisabstract.com' TA_REPORTS_BASE = f"{BASE_URL}/reports" REQUEST_DELAY = 0.5 # Delay between requests in seconds REQUEST_RETRIES = 3 # Number of retries for failed requests ``` -------------------------------- ### Format Player Form String Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/career.html Formats a string representing a player's recent match results (W for win, L for loss) into HTML badges. Displays up to the first 10 results. ```javascript function displayFormString(formString) { let html = ''; for (let i = 0; i < formString.length && i < 10; i++) { const result = formString[i]; html += `${result}`; } return html; } ``` -------------------------------- ### Generate Year and Surface Navigation - JavaScript Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/career.html Dynamically generates HTML for year and surface navigation buttons based on response data. Includes event listeners for displaying statistics. ```javascript contentHtml += ''; const sortedYears = response.yearly_stats.map(y => y.year).sort((a, b) => b - a); sortedYears.forEach(year => { contentHtml += ` `; }); contentHtml += ''; contentHtml += `
`; const availableSurfaces = Object.keys(response.surface_breakdown || {}); availableSurfaces.forEach(surface => { contentHtml += ` `; }); contentHtml += '
'; contentHtml += '
'; // *** MODIFICATION: Changed header to bg-dark text-white *** contentHtml += `
Performance Trend
`; $('#results').html(heroHtml + contentHtml); setTimeout(() => { displayYearStats('career', 'All'); }, 100); ``` -------------------------------- ### Career Form Submission Handler Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/career.html Handles AJAX submission for career data and dynamically renders the hero section with player statistics and recent form. ```javascript $('#careerForm').on('submit', function(e) { e.preventDefault(); $('.loading').show(); $('.error-message').hide(); $('#results').empty(); $.ajax({ url: '/career', method: 'POST', data: $(this).serialize(), success: function(response) { $('.loading').hide(); careerData = response; currentYear = 'career'; currentSurface = 'All'; let heroHtml = `

${response.player}

Career Record
${response.career_summary['W-L']}

${response.career_summary['win%']}% Win Rate

Avg Opponent Rank
${response.career_summary['avg_opp_rank']}
Years on Tour
${response.yearly_stats.length}
Recent Form
${displayFormString(response.recent_form.form_string)}

Last ${response.recent_form.last_matches}: ${response.recent_form.wins}-${response.recent_form.losses} (${response.recent_form.win_pct}%)

${response.recent_form.win_streak > 1 ? `

${response.recent_form.win_streak} match win streak

` : response.recent_form.loss_streak > 1 ? `

${response.recent_form.loss_streak} match losing streak

` : ''}
`; let contentHtml = '
'; contentHtml += '
{ const isWin = match.wl === 'W'; const s = (match.surf || '').toString().toLowerCase(); html += `
${match.date} ${match.tourn} ${match.round}
${match.surf || '-'} ${isWin ? 'def.' : 'lost to'} ${match.opp} ${match.score}
`; }); html += '
'; return html; } ``` -------------------------------- ### Jinja Template Structure for Player Comparison Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/compare.html This Jinja template defines the overall structure for the player comparison page, including placeholders for player names, year, and comparison results. It also includes logic for iterating through years. ```html {% extends "base.html" %} {% block content %} Player Comparison ----------------- Player 1 Player 2 Year Career {% for year in range(current_year, 1999, -1) %} {{ year }} {% endfor %} Compare Players Loading... Analyzing player statistics... {% endblock %} {% block extra_js %} ``` -------------------------------- ### Head to Head Analysis Frontend Logic Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/h2h.html Handles form submission, AJAX data fetching, and dynamic DOM manipulation for displaying match history and charts. ```javascript function getSurfaceClass(surface) { const surfaceMap = { 'Hard': 'surface-hard', 'Clay': 'surface-clay', 'Grass': 'surface-grass', 'Carpet': 'surface-hard' }; return surfaceMap[surface] || 'surface-hard'; } let h2hProgressChart = null, h2hSurfacesChart = null, h2hRoundsChart = null; $('#h2hForm').on('submit', function(e) { e.preventDefault(); $('.loading').show(); $('.error-message').hide(); $('#results').empty(); $.ajax({ url: '/h2h', method: 'POST', data: $(this).serialize(), success: function(response) { $('.loading').hide(); const p1 = response.summary.player1; const p2 = response.summary.player2; // Hero summary let summaryHtml = `

${p1} vs ${p2}

${response.summary.p1_wins}
${p1} Wins
${response.summary.total_matches}
Total Matches
${response.summary.p2_wins}
${p2} Wins
`; // Charts section let chartsHtml = `
Cumulative Wins
Wins by Surface
Wins by Round
`; // Matches list let matchesHtml = `
Match History
`; response.matches.forEach((match) => { const isP1Winner = match.winner_name === p1; matchesHtml += `
${match.match_date}
${match.surface}
${match.tournament}
${match.round}
${p1} ${isP1Winner ? '' : ''}
${match.score}
${p2} ${!isP1Winner ? '' : ''}
${match.h2h}
`; }); matchesHtml += '
'; $('#results').html(summaryHtml + chartsHtml + matchesHtml); // Build cumulative wins data const byDate = [...response.matches].sort((a,b)=> new Date(a.match_date) - new Date(b.match_date)); let w1 = 0, w2 = 0; const labels = [], s1 = [], s2 = []; byDate.forEach(m => { labels.push(m.match_date); if (m.winner_name === p1) w1++; else if (m.winner_name === p2) w2++; s1.push(w1); s2.push(w2); }); // Calculate wins by round const roundOrder = ['F', 'SF', 'QF', 'R16', 'R32', 'R64', 'R128', 'RR']; const roundLabels = {'F': 'Final', 'SF': 'Semi-Final', 'QF': 'Quarter-Final', 'R16': 'Round of 16', 'R32': 'Round of 32', 'R64': 'Round of 64', 'R128': 'Round of 128', 'RR': 'Round Robin'}; const roundStats = {}; response.matches.forEach(match => { const round = match.round || 'Other'; if (!roundStats[round]) roundStats[round] = { [p1]: 0, [p2]: 0 }; if (match.winner_name === p1) roundStats[round][p1]++; else if (match.winner_name === p2) roundStats[round][p2]++; }); const sortedRounds = Object.keys(roundStats).sort((a, b) => { const aIndex = roundOrder.indexOf(a), bIndex = roundOrder.indexOf(b); if (aIndex === -1 && bIndex === -1) return 0; if (aIndex === -1) return 1; if (bIndex === -1) return -1; return aIndex - bIndex; }); const roundLabelsArray = sortedRounds.map(r => roundLabels[r] || r); const p1RoundWins = sortedRounds.map(r => roundStats[r][p1] || 0); const p2RoundWins = sortedRounds.map(r => roundStats[r][p2] || 0); // Destroy old charts [h2hProgressChart, h2hSurfacesChart, h2hRoundsChart].forEach(ch => { if (ch && ch.destroy) ch.destro ``` -------------------------------- ### Handle Comparison Form Submission Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/compare.html Processes the AJAX request for player comparison data and dynamically updates the UI with surface selection buttons. ```javascript $('#compareForm').on('submit', function(e) { e.preventDefault(); $('.loading').show(); $('.error-message').hide(); $('#results').empty(); $('#surfaceFilter').hide(); $.ajax({ url: '/compare', method: 'POST', data: $(this).serialize(), success: function(response) { $('.loading').hide(); allSurfacesData = response.all_surfaces_stats; const available = Object.keys(allSurfacesData).filter(s => allSurfacesData[s] && allSurfacesData[s].length); if (available.length) { const btns = available.map((s, i) => ``).join(''); $('#surfaceFilter .btn-group').html(btns); $('#surfaceFilter').show(); currentSurface = available[0]; displayComparison(currentSurface); } else { $('#results').html('
No comparison data found for the selected players and year.
'); } }, error: function(xhr) { $('.loading').hide(); let errorMsg = xhr.responseJSON ? xhr.responseJSON.error : 'An error occurred'; $('.error-message').text(errorMsg).show(); } ``` -------------------------------- ### Calculate Yearly Statistics with Python Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Computes aggregated yearly statistics from match data, with support for surface-specific filtering. ```python from mod import get_player_matches, calculate_yearly_stats matches = get_player_matches("Novak Djokovic") # Calculate overall yearly stats yearly_stats = calculate_yearly_stats(matches) print(yearly_stats.loc[2023, ['W-L', 'win%', 'ace%', '1st_win%', 'tb_win%', 'top10_win%']]) # W-L 55-8 # win% 87.3 # ace% 7.82 # 1st_win% 76.4 # tb_win% 62.5 # top10_win% 73.3 # Filter by surface clay_stats = calculate_yearly_stats(matches, surface='Clay') print(clay_stats.loc[2023, ['W-L', 'win%', 'break%']]) # W-L 12-2 # win% 85.7 # break% 42.1 ``` -------------------------------- ### Strip Percentage Helper Function - JavaScript Source: https://github.com/thecommishdeuce/tennisabstract/blob/main/templates/career.html A helper function to parse percentage strings into floating-point numbers, returning null for invalid inputs. Used for chart data preparation. ```javascript function _stripPct(val) { if (val === null || val === undefined) return null; const num = parseFloat(val); return isFinite(num) ? num : null; } ``` -------------------------------- ### Compare Players by Year Source: https://context7.com/thecommishdeuce/tennisabstract/llms.txt Compares statistics for two specified players for a given year across all surfaces. Requires player names and the year as form-urlencoded data in a POST request. ```bash # Compare two players for a specific year curl -X POST "http://localhost:5002/compare" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "player1=Novak Djokovic&player2=Carlos Alcaraz&year=2024" ``` ```json { "all_surfaces_stats": { "All": [ {"stat": "W-L", "player1_value": "38-8", "player2_value": "54-12", "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "win%", "player1_value": 82.6, "player2_value": 81.8, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "ace%", "player1_value": 8.45, "player2_value": 9.12, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "1st_win%", "player1_value": 75.2, "player2_value": 77.8, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "bp_saved%", "player1_value": 64.3, "player2_value": 61.5, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "tb_win%", "player1_value": 58.3, "player2_value": 65.0, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"}, {"stat": "top10_win%", "player1_value": 66.7, "player2_value": 71.4, "player1_name": "Novak Djokovic", "player2_name": "Carlos Alcaraz"} ], "Hard": [...], "Clay": [...], "Grass": [...] }, "player1": "Novak Djokovic", "player2": "Carlos Alcaraz", "year": 2024 } ```