Session 9: api-football + FootApi + Tank01 adapters, grace period middleware, cookie consent, /pricing page, OOM fix documented (1240 tests)
This commit is contained in:
@@ -39,27 +39,66 @@ async function safeCacheGet(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// Read per-player season aggregate. The prefetch writes a flat shape
|
||||
// that already collapses played + minutes into per-90 rates so we don't
|
||||
// recompute on every request.
|
||||
// Source-priority cascade (Session 9). Each load function checks
|
||||
// adapter-specific keys in priority order:
|
||||
// 1. api-football.com (PRIMARY — 100 req/day, richest payload)
|
||||
// 2. FootApi via RapidAPI (BACKUP — 50 req/day, Sofascore mirror)
|
||||
// 3. football-data.org (TERTIARY — fixtures/standings only)
|
||||
//
|
||||
// The richer adapters write name-keyed aliases (`apifootball:player_by_name:…`,
|
||||
// `footapi:player_by_name:…`) during the daily prefetch so the request
|
||||
// path can resolve without an upstream call. When the alias key is
|
||||
// missing — either because the prefetch hasn't run, the adapter has
|
||||
// no key configured, or the player isn't covered — we fall through to
|
||||
// the next source. Final fallback is the legacy `soccer:player:{name}`
|
||||
// key the football-data prefetch already populates.
|
||||
//
|
||||
// Every value is tagged with `_source` so trap detection and reasoning
|
||||
// can attribute the data origin (and so we can spot when a source is
|
||||
// silently failing in production logs).
|
||||
async function loadFromCascade(keys) {
|
||||
for (const { key, source } of keys) {
|
||||
const v = await safeCacheGet(key);
|
||||
if (v && typeof v === 'object') {
|
||||
return { ...v, _source: v._source || source };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loadPlayerProfile(playerName) {
|
||||
if (!playerName) return null;
|
||||
return safeCacheGet(`soccer:player:${normalizeName(playerName)}`);
|
||||
const n = normalizeName(playerName);
|
||||
return loadFromCascade([
|
||||
{ key: `apifootball:player_by_name:${n}`, source: 'api-football' },
|
||||
{ key: `footapi:player_by_name:${n}`, source: 'footapi' },
|
||||
{ key: `soccer:player:${n}`, source: 'football-data' },
|
||||
]);
|
||||
}
|
||||
|
||||
async function loadNextMatch(teamName) {
|
||||
if (!teamName) return null;
|
||||
return safeCacheGet(`soccer:nextmatch:${teamName}`);
|
||||
return loadFromCascade([
|
||||
{ key: `apifootball:nextmatch:${teamName}`, source: 'api-football' },
|
||||
{ key: `soccer:nextmatch:${teamName}`, source: 'football-data' },
|
||||
]);
|
||||
}
|
||||
|
||||
async function loadLastFixture(teamName) {
|
||||
if (!teamName) return null;
|
||||
return safeCacheGet(`soccer:lastfixture:${teamName}`);
|
||||
return loadFromCascade([
|
||||
{ key: `apifootball:lastfixture:${teamName}`, source: 'api-football' },
|
||||
{ key: `soccer:lastfixture:${teamName}`, source: 'football-data' },
|
||||
]);
|
||||
}
|
||||
|
||||
async function loadRefereeProfile(refName) {
|
||||
if (!refName) return null;
|
||||
return safeCacheGet(`soccer:referee:${refName}`);
|
||||
return loadFromCascade([
|
||||
{ key: `apifootball:referee_by_name:${refName}`, source: 'api-football' },
|
||||
{ key: `footapi:referee_by_name:${refName}`, source: 'footapi' },
|
||||
{ key: `soccer:referee:${refName}`, source: 'football-data' },
|
||||
]);
|
||||
}
|
||||
|
||||
async function loadTeamDefense(league, teamName) {
|
||||
@@ -221,6 +260,16 @@ async function extractSoccerFeatures(input = {}) {
|
||||
isHome,
|
||||
gameLogs: [],
|
||||
errors,
|
||||
// Session 9 — which source the cascade actually resolved for
|
||||
// each load. Useful for debugging "why does Messi's profile
|
||||
// look thin?" — if `player_source: 'football-data'` it means
|
||||
// the api-football prefetch hasn't populated his row yet.
|
||||
sources: {
|
||||
player: profile?._source || null,
|
||||
nextMatch: nextMatch?._source || null,
|
||||
lastFixture: lastFixture?._source || null,
|
||||
referee: refProfile?._source || null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user