/** * FIFA World Cup 2026 reference data — June 11–July 19, 2026. * * Static, hand-curated. The poller pulls fixtures/standings/squads from * APIs (football-data.org + worldcup2026 OSS); this file holds the * intelligence the APIs don't carry: venue altitudes, host-continent * teams, designated penalty/set-piece takers, and historical tournament * performers. * * Updated manually as the tournament progresses (squad confirmations, * penalty-taker shifts, etc.). Not consumed during normal cache misses — * the feature extractor reads this once per process load. * * Venue altitudes sourced from public elevation data (NOAA / Google * Earth, ft above sea level). Estadio Akron is in Zapopan (Guadalajara * metro) at ~5,100 ft; Estadio Azteca's stadium floor sits at ~7,349 ft * — the highest altitude in tournament history for the host venue. */ const VENUES = Object.freeze({ // United States (11) 'AT&T Stadium': { city: 'Arlington, TX', altitude_ft: 600, climate: 'hot', country: 'USA' }, 'Mercedes-Benz Stadium': { city: 'Atlanta, GA', altitude_ft: 1050, climate: 'hot_humid', country: 'USA' }, 'Gillette Stadium': { city: 'Foxborough, MA', altitude_ft: 260, climate: 'temperate', country: 'USA' }, 'NRG Stadium': { city: 'Houston, TX', altitude_ft: 80, climate: 'hot_humid', country: 'USA' }, 'Arrowhead Stadium': { city: 'Kansas City, MO', altitude_ft: 820, climate: 'temperate', country: 'USA' }, 'SoFi Stadium': { city: 'Inglewood, CA', altitude_ft: 100, climate: 'temperate', country: 'USA' }, 'Hard Rock Stadium': { city: 'Miami Gardens, FL', altitude_ft: 10, climate: 'hot_humid', country: 'USA' }, 'MetLife Stadium': { city: 'East Rutherford, NJ', altitude_ft: 7, climate: 'temperate', country: 'USA' }, 'Lincoln Financial Field': { city: 'Philadelphia, PA', altitude_ft: 30, climate: 'temperate', country: 'USA' }, "Levi's Stadium": { city: 'Santa Clara, CA', altitude_ft: 33, climate: 'temperate', country: 'USA' }, 'Lumen Field': { city: 'Seattle, WA', altitude_ft: 15, climate: 'cool', country: 'USA' }, // Canada (2) 'BMO Field': { city: 'Toronto, ON', altitude_ft: 250, climate: 'temperate', country: 'Canada' }, 'BC Place': { city: 'Vancouver, BC', altitude_ft: 33, climate: 'cool', country: 'Canada' }, // Mexico (3) 'Estadio Azteca': { city: 'Mexico City', altitude_ft: 7349, climate: 'altitude', country: 'Mexico' }, 'Estadio BBVA': { city: 'Monterrey', altitude_ft: 1765, climate: 'hot', country: 'Mexico' }, 'Estadio Akron': { city: 'Guadalajara (Zapopan)', altitude_ft: 5138, climate: 'altitude', country: 'Mexico' }, }); // Host-continent teams (CONCACAF) — historically benefit from reduced // travel, fan support, and altitude acclimation. The 2026 hosts (USA, // Canada, Mexico) auto-qualified; the rest qualified through CONCACAF. const CONCACAF_TEAMS = Object.freeze([ 'USA', 'Canada', 'Mexico', 'Costa Rica', 'Jamaica', 'Honduras', 'Panama', 'El Salvador', 'Haiti', 'Trinidad and Tobago', 'Guatemala', 'Curacao', 'Suriname', ]); // CONMEBOL teams travel less than European/African squads and are // historically strong in 2026-style climates. Used as a softer secondary // modifier (not full home-continent advantage). const CONMEBOL_TEAMS = Object.freeze([ 'Argentina', 'Brazil', 'Uruguay', 'Colombia', 'Ecuador', 'Paraguay', 'Peru', 'Chile', 'Venezuela', 'Bolivia', ]); // Designated penalty takers — primary plus secondary fallback if primary // is off the pitch. Sourced from each team's most recent qualifier or // pre-tournament friendly; updated as confirmations come in. // // Penalty-taker status adds ~0.15 goals per 90 to the player's base rate. // Keys are team names matching football-data.org's `team.name` field; // values are arrays in preference order. const PENALTY_TAKERS = Object.freeze({ 'Argentina': ['Lionel Messi', 'Lautaro Martínez'], 'Brazil': ['Vinicius Junior', 'Neymar'], 'France': ['Kylian Mbappé', 'Antoine Griezmann'], 'England': ['Harry Kane', 'Bukayo Saka'], 'Portugal': ['Cristiano Ronaldo', 'Bruno Fernandes'], 'Spain': ['Álvaro Morata', 'Mikel Merino'], 'Germany': ['Kai Havertz', 'İlkay Gündoğan'], 'Netherlands': ['Memphis Depay', 'Cody Gakpo'], 'Belgium': ['Romelu Lukaku', 'Kevin De Bruyne'], 'Italy': ['Jorginho', 'Lorenzo Pellegrini'], 'Croatia': ['Luka Modrić', 'Andrej Kramarić'], 'Uruguay': ['Darwin Núñez', 'Federico Valverde'], 'Colombia': ['James Rodríguez', 'Luis Díaz'], 'Mexico': ['Raúl Jiménez', 'Hirving Lozano'], 'USA': ['Christian Pulisic', 'Folarin Balogun'], 'Canada': ['Jonathan David', 'Alphonso Davies'], 'Morocco': ['Hakim Ziyech', 'Achraf Hakimi'], 'Senegal': ['Sadio Mané', 'Ismaïla Sarr'], 'Japan': ['Takefusa Kubo', 'Wataru Endō'], 'South Korea': ['Son Heung-min', 'Hwang Hee-chan'], 'Australia': ['Jackson Irvine', 'Mitchell Duke'], 'Switzerland': ['Granit Xhaka', 'Xherdan Shaqiri'], 'Poland': ['Robert Lewandowski', 'Piotr Zieliński'], 'Denmark': ['Christian Eriksen', 'Pierre-Emile Højbjerg'], 'Serbia': ['Aleksandar Mitrović', 'Dušan Vlahović'], }); // Designated corner takers (set-piece delivery role). Multi-name arrays // reflect rotation; the first is the most common deliverer. Corner-taker // status meaningfully boosts assist probability for headed goals. const CORNER_TAKERS = Object.freeze({ 'Argentina': ['Lionel Messi', 'Ángel Di María'], 'Brazil': ['Lucas Paquetá', 'Bruno Guimarães'], 'France': ['Antoine Griezmann', 'Kylian Mbappé'], 'England': ['Bukayo Saka', 'Phil Foden', 'Trent Alexander-Arnold'], 'Portugal': ['Bruno Fernandes', 'João Cancelo'], 'Spain': ['Dani Olmo', 'Mikel Merino'], 'Germany': ['Joshua Kimmich', 'Toni Kroos'], 'Netherlands': ['Frenkie de Jong', 'Cody Gakpo'], 'Belgium': ['Kevin De Bruyne', 'Yannick Carrasco'], 'Italy': ['Lorenzo Pellegrini', 'Federico Chiesa'], 'Croatia': ['Luka Modrić', 'Mateo Kovačić'], 'Uruguay': ['Federico Valverde', 'Giorgian de Arrascaeta'], 'Mexico': ['Andrés Guardado', 'Hirving Lozano'], 'USA': ['Christian Pulisic', 'Gio Reyna'], 'Canada': ['Stephen Eustáquio', 'Tajon Buchanan'], }); // Direct free-kick specialists. These players take long-range and // dangerous-area free kicks. Boosts both goal AND assist probability // when a foul is drawn in shooting range. const FREE_KICK_TAKERS = Object.freeze({ 'Argentina': ['Lionel Messi'], 'Brazil': ['Neymar', 'Vinicius Junior'], 'France': ['Kylian Mbappé'], 'England': ['Trent Alexander-Arnold', 'Bukayo Saka'], 'Portugal': ['Cristiano Ronaldo', 'Bruno Fernandes'], 'Spain': ['Dani Olmo'], 'Germany': ['Joshua Kimmich'], 'Belgium': ['Kevin De Bruyne'], 'Italy': ['Federico Chiesa'], 'Croatia': ['Luka Modrić'], 'Colombia': ['James Rodríguez'], 'Mexico': ['Raúl Jiménez'], 'USA': ['Christian Pulisic'], 'Morocco': ['Hakim Ziyech'], 'South Korea': ['Son Heung-min'], 'Poland': ['Piotr Zieliński'], 'Serbia': ['Dušan Tadić'], }); // "Tournament players" — historical World Cup performers with three or // more career WC goals. These names lift the prior on big-game scoring. // Threshold: >=3 career WC goals OR >=2 in the most recent WC. const TOURNAMENT_PLAYERS = Object.freeze({ 'Lionel Messi': { wc_goals_career: 13, wc_appearances: 26 }, 'Cristiano Ronaldo': { wc_goals_career: 8, wc_appearances: 22 }, 'Kylian Mbappé': { wc_goals_career: 12, wc_appearances: 14 }, 'Harry Kane': { wc_goals_career: 8, wc_appearances: 11 }, 'Neymar': { wc_goals_career: 8, wc_appearances: 16 }, 'Olivier Giroud': { wc_goals_career: 5, wc_appearances: 18 }, 'Antoine Griezmann': { wc_goals_career: 6, wc_appearances: 17 }, 'Romelu Lukaku': { wc_goals_career: 5, wc_appearances: 11 }, 'Luka Modrić': { wc_goals_career: 2, wc_appearances: 18 }, // captain bias 'Robert Lewandowski': { wc_goals_career: 2, wc_appearances: 8 }, 'Karim Benzema': { wc_goals_career: 3, wc_appearances: 11 }, 'Edinson Cavani': { wc_goals_career: 4, wc_appearances: 14 }, 'Luis Suárez': { wc_goals_career: 7, wc_appearances: 14 }, 'Andrés Guardado': { wc_goals_career: 1, wc_appearances: 20 }, // captain bias 'Thomas Müller': { wc_goals_career: 10, wc_appearances: 16 }, 'Eden Hazard': { wc_goals_career: 3, wc_appearances: 11 }, 'Hirving Lozano': { wc_goals_career: 2, wc_appearances: 7 }, 'Sadio Mané': { wc_goals_career: 1, wc_appearances: 5 }, }); // Lookup helpers — case-insensitive on player names, exact-match on // team names. Each returns a primitive so the feature extractor can // drop the result straight into the feature vector. function isPenaltyTaker(playerName, teamName) { if (!playerName || !teamName) return false; const takers = PENALTY_TAKERS[teamName]; if (!Array.isArray(takers)) return false; const p = String(playerName).toLowerCase(); return takers.some((t) => t.toLowerCase() === p); } function isCornerTaker(playerName, teamName) { if (!playerName || !teamName) return false; const takers = CORNER_TAKERS[teamName]; if (!Array.isArray(takers)) return false; const p = String(playerName).toLowerCase(); return takers.some((t) => t.toLowerCase() === p); } function isFreeKickTaker(playerName, teamName) { if (!playerName || !teamName) return false; const takers = FREE_KICK_TAKERS[teamName]; if (!Array.isArray(takers)) return false; const p = String(playerName).toLowerCase(); return takers.some((t) => t.toLowerCase() === p); } function getTournamentHistory(playerName) { if (!playerName) return null; // Exact match first, then case-insensitive scan. if (TOURNAMENT_PLAYERS[playerName]) return TOURNAMENT_PLAYERS[playerName]; const p = String(playerName).toLowerCase(); for (const [name, history] of Object.entries(TOURNAMENT_PLAYERS)) { if (name.toLowerCase() === p) return history; } return null; } function isHomeContinent(teamName) { if (!teamName) return false; return CONCACAF_TEAMS.includes(teamName); } function getVenue(venueName) { if (!venueName) return null; return VENUES[venueName] || null; } // Classify altitude impact for non-acclimatized teams. The historical // goal-output reduction kicks in around 1,500 ft and gets material above // 4,000 ft (per CSIC studies on player physiology at altitude). function altitudeImpact(altitudeFt) { if (!Number.isFinite(altitudeFt)) return 'none'; if (altitudeFt >= 4000) return 'high'; if (altitudeFt >= 1500) return 'moderate'; return 'none'; } module.exports = { VENUES, CONCACAF_TEAMS, CONMEBOL_TEAMS, PENALTY_TAKERS, CORNER_TAKERS, FREE_KICK_TAKERS, TOURNAMENT_PLAYERS, isPenaltyTaker, isCornerTaker, isFreeKickTaker, getTournamentHistory, isHomeContinent, getVenue, altitudeImpact, };