predictions = fetchParquet(base_url + "football/predictions.parquet")
ratings = fetchParquet(base_url + "football/ratings.parquet")
matchStatsRaw = paramLeague ? fetchParquet(base_url + "football/match-stats-" + paramLeague + ".parquet") : null
matchShotsRaw = fetchParquet(base_url + "football/match-shots.parquet")
chainsRaw = {
const rows = paramLeague ? await fetchParquet(base_url + "football/chains-" + paramLeague + ".parquet") : null
// Forward-compat: pannadata may rename the per-action EPV CREDIT column
// `equity`→`epv_credit` (it holds credit, not a state — see CLAUDE.md). Alias
// it so every downstream `.equity` reader keeps working through the rename
// without a coordinated cross-repo deploy.
if (rows) for (const r of rows) if (r.equity == null && r.epv_credit != null) r.equity = r.epv_credit
return rows
}
// Batch per-player WPA for this match from game-logs.parquet — the SAME values
// the season Value tab shows. The worker's LIVE WPA over-credits (~2× the batch)
// because it scores raw Opta events without SPADL receiver attribution, so for
// FINISHED matches the Value tab prefers these batch values (the worker value is
// only used while a match is in progress, before game-logs is built). Returns a
// Map(player_name → row) or an empty Map. The default game-logs.parquet covers the
// current season; older matches fall back to football/game-logs-<season>.parquet.
_matchGameLogs = {
const mid = match && match.match_id
if (!mid) return new Map()
const pick = (rows) => (rows || []).filter(d => d.match_id === mid)
let rows = pick(await fetchParquet(base_url + "football/game-logs.parquet")
.catch(e => { console.warn("[match] game-logs.parquet load failed:", e.message); return null }))
if (rows.length === 0 && paramDate) {
const d = new Date(paramDate), y = d.getUTCFullYear(), m = d.getUTCMonth() + 1
const start = m >= 7 ? y : y - 1
const season = `${start}-${String(start + 1).slice(2)}` // e.g. "2025-26"
rows = pick(await fetchParquet(base_url + `football/game-logs-${season}.parquet`)
.catch(e => { console.warn(`[match] game-logs-${season}.parquet unavailable:`, e.message); return null }))
}
const map = new Map()
for (const r of rows) if (r.player_name != null) map.set(r.player_name, r)
if (map.size > 0) console.log(`[match] batch WPA loaded for ${map.size} players (game-logs)`)
return map
}
fixtures = {
// Load from R2 first (worker currently returns incomplete data)
try {
const res = await fetch(window.DATA_BASE_URL + "football/fixtures.json")
if (res.ok) { const data = await res.json(); return data.matches || null }
} catch (e) { console.warn("[match] R2 fixture fetch failed, falling back to Worker:", e.message) }
const data = await window.fetchFixtures("football")
return data ? (data.matches || null) : null
}