Session 7j: Soccer intelligence - 9 leagues, 11 signals, 6 traps, poller, prefetch, 131 new tests (1173 total)
This commit is contained in:
@@ -29,6 +29,9 @@ const featureCache = require('./featureCache');
|
||||
const trapDetection = require('./trapDetection');
|
||||
const consistencyScore = require('./consistencyScore');
|
||||
const gameLogService = require('./gameLogService');
|
||||
// Session 7j — soccer branch. The extractor reads from prefetched
|
||||
// Redis cache; no external HTTP on the user request path.
|
||||
const { extractSoccerFeatures, isSoccerSport } = require('./soccerFeatureExtractor');
|
||||
|
||||
const HTTP_TIMEOUT_MS = 8_000;
|
||||
|
||||
@@ -121,14 +124,37 @@ async function safeGetConsistency({ playerName, sport, statType }) {
|
||||
}
|
||||
|
||||
async function computeFeaturesForProp(rawProp = {}) {
|
||||
// Default to NBA when caller omits — matches what legacy analyzeProp does.
|
||||
const sport = String(rawProp.sport || 'nba').toLowerCase();
|
||||
|
||||
// Soccer routes to a different extractor — different data sources
|
||||
// (football-data.org + cache vs ESPN scoreboard), different feature
|
||||
// set (xG, altitude, referee, set-piece role). The extractor returns
|
||||
// the same {features, trap, consistency, prop, meta} shape engine1
|
||||
// consumes, so analyzeViaEngine1 is sport-agnostic downstream.
|
||||
if (isSoccerSport(sport)) {
|
||||
const soccerResult = await extractSoccerFeatures(rawProp);
|
||||
// Soccer extractor returns a placeholder trap object. Run the real
|
||||
// soccer-branch trap detection here using the freshly computed
|
||||
// features so analyzeViaEngine1 sees a populated trap composite.
|
||||
const soccerTrap = await safeGetTrap({
|
||||
sport: 'soccer',
|
||||
playerName: rawProp.player,
|
||||
statType: soccerResult.meta?.statType,
|
||||
gameId: null,
|
||||
gameContext: { home_away: soccerResult.features?.home_away === 1.0 ? 'home' : (soccerResult.features?.home_away === 0.0 ? 'away' : null) },
|
||||
features: soccerResult.features,
|
||||
odds: { playerLine: soccerResult.prop?.line, consensus: null },
|
||||
});
|
||||
return { ...soccerResult, trap: soccerTrap };
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const player = rawProp.player;
|
||||
const statType = rawProp.stat_type || rawProp.statType;
|
||||
const line = Number(rawProp.line);
|
||||
const direction = rawProp.direction || 'over';
|
||||
const book = rawProp.book || 'unknown';
|
||||
// Default to NBA when caller omits — matches what legacy analyzeProp does.
|
||||
const sport = (rawProp.sport || 'nba').toLowerCase();
|
||||
|
||||
if (!player || !statType || !Number.isFinite(line)) {
|
||||
errors.push('missing required fields (player, stat_type, or line)');
|
||||
|
||||
Reference in New Issue
Block a user