Session 15: Intelligence hardening — park factors, weather, Tank01 prefetch, pace factors, signal audit, founder pricing fix (1405 tests)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,54 @@
|
||||
*
|
||||
* The caller (analyzeViaEngine1) reads the returned `errors` array and
|
||||
* downgrades confidence accordingly via the adapter's reasoning string.
|
||||
*
|
||||
* ─────────────────────────────────────────────────────────────────────
|
||||
* Signal provenance (Session 15 audit)
|
||||
* ─────────────────────────────────────────────────────────────────────
|
||||
* Every signal the engine reads has a documented source. Phantom
|
||||
* signals — referenced in reasoning but populated by nothing — would
|
||||
* be a trust failure. As of Session 15 there are none.
|
||||
*
|
||||
* • injury_severity_score (engine1.js:126 reads it; analyzeViaEngine1
|
||||
* surfaces it in reasoning at line 156)
|
||||
* ← `src/services/intelligence/injuryParser.js` (ESPN injury feed)
|
||||
* Populated by the grading orchestrator in batch mode; in the
|
||||
* single-prop path it lives in the `featureCache` payload.
|
||||
*
|
||||
* • coach_pace_delta + coach_player_interaction
|
||||
* ← `src/services/intelligence/coachSignals.js` reads the
|
||||
* `coach_profiles` Supabase table (migration 017), with a
|
||||
* `src/config/coaches.json` seed file as the cold-start fallback.
|
||||
*
|
||||
* • consistency (boom_bust / reliable / elite labels + numeric score)
|
||||
* ← `src/services/intelligence/consistencyScore.js` operating on
|
||||
* game logs from `gameLogService` (ESPN). When game logs are
|
||||
* unavailable, defaults to `{consistency:'unknown', score:null}`
|
||||
* which engine1 treats as neutral (does not penalize).
|
||||
*
|
||||
* • Tank01 t01_* fields (added Session 14)
|
||||
* ← `src/services/intelligence/tank01Augment.js` reads cache keys
|
||||
* written by `scripts/tank01-prefetch.js` (Session 15 — added
|
||||
* this session) which calls the Tank01 NBA/MLB RapidAPI adapters.
|
||||
*
|
||||
* • Soccer features (10 of them — goals_per_90, xG, altitude, etc.)
|
||||
* ← `src/services/intelligence/soccerFeatureExtractor.js` cascade
|
||||
* across api-football → footapi → football-data cache keys.
|
||||
*
|
||||
* • Park factors (Session 15 — MLB)
|
||||
* ← `src/data/parkFactors.js` — static FanGraphs 2024-25 data.
|
||||
*
|
||||
* • Weather (Session 15 — MLB + soccer)
|
||||
* ← `src/services/weatherService.js` calls Open-Meteo (no key),
|
||||
* cached 1h in Redis. Skipped for dome stadiums.
|
||||
*
|
||||
* • Pace factors (Session 15 — NBA)
|
||||
* ← `src/data/paceFactors.js` — static NBA team pace data.
|
||||
*
|
||||
* No signal currently surfaces in user-facing reasoning that isn't
|
||||
* populated by one of the sources above. When a source is down, the
|
||||
* signal returns null and reasoning omits it gracefully — never
|
||||
* fabricated.
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
@@ -37,6 +85,15 @@ const { extractSoccerFeatures, isSoccerSport } = require('./soccerFeatureExtract
|
||||
// populates the cache. Until that lands, the augmentor returns
|
||||
// empty objects and the existing ESPN-derived features stand alone.
|
||||
const tank01Augment = require('./tank01Augment');
|
||||
// Session 15 — static lookup tables (MLB park factors, NBA pace
|
||||
// factors). Pure synchronous reads, no network, no cache. Merged
|
||||
// into the feature map alongside the per-sport ESPN payload.
|
||||
const { getParkFactor } = require('../../data/parkFactors');
|
||||
const { getPaceFactor } = require('../../data/paceFactors');
|
||||
// Session 15 — Open-Meteo weather fetch. 1h Redis cache, 5s timeout,
|
||||
// silent on failure. Skipped for dome stadiums via the venue index.
|
||||
const weatherService = require('../weatherService');
|
||||
const { getMlbVenue, getWcVenueCoords } = require('../../data/venueCoordinates');
|
||||
|
||||
const HTTP_TIMEOUT_MS = 8_000;
|
||||
|
||||
@@ -224,6 +281,58 @@ async function computeFeaturesForProp(rawProp = {}) {
|
||||
console.warn('[computeFeatures] Tank01 augmentation skipped:', err.message);
|
||||
}
|
||||
|
||||
// Session 15 — static context augmentation. Park factors (MLB),
|
||||
// pace factors (NBA). Synchronous, can't fail; the lookups return
|
||||
// null on miss, which we treat as "no signal — drop the field".
|
||||
try {
|
||||
if (sport === 'mlb') {
|
||||
// Home team in this matchup hosts the game; if the player's
|
||||
// team is home, use their abbr — otherwise use the opponent's.
|
||||
const homeAbbr = game?.isHome ? teamAbbr : game?.opponentAbbr;
|
||||
const park = getParkFactor(homeAbbr);
|
||||
if (park) {
|
||||
features.park_hr = park.hr;
|
||||
features.park_h = park.h;
|
||||
features.park_r = park.r;
|
||||
features.park_home = homeAbbr;
|
||||
}
|
||||
} else if (sport === 'nba') {
|
||||
// Pace factors are per-team — use the player's own team (fast
|
||||
// teams up the count regardless of opponent, slow teams
|
||||
// compress). Opponent pace effect is a separate signal we
|
||||
// could layer in a follow-up.
|
||||
const pace = getPaceFactor(teamAbbr);
|
||||
if (pace != null) features.pace_factor = pace;
|
||||
const oppPace = getPaceFactor(game?.opponentAbbr);
|
||||
if (oppPace != null) features.opp_pace_factor = oppPace;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[computeFeatures] static context augmentation skipped:', err.message);
|
||||
}
|
||||
|
||||
// Session 15 — weather. Open-Meteo via weatherService. 5s timeout,
|
||||
// 1h Redis cache, dome-aware skip. Outdoor MLB + soccer benefit;
|
||||
// basketball indoor venues skip entirely.
|
||||
try {
|
||||
if (sport === 'mlb') {
|
||||
const homeAbbr = game?.isHome ? teamAbbr : game?.opponentAbbr;
|
||||
const venue = homeAbbr ? getMlbVenue(homeAbbr) : null;
|
||||
if (venue && !venue.dome && Number.isFinite(venue.lat) && Number.isFinite(venue.lon)) {
|
||||
const w = await weatherService.getWeather(venue.lat, venue.lon);
|
||||
if (w) {
|
||||
features.weather_temp_f = w.temp_f ?? null;
|
||||
features.weather_wind_mph = w.wind_mph ?? null;
|
||||
features.weather_wind_dir = w.wind_dir ?? null;
|
||||
features.weather_precip = w.precip_mm ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Soccer weather slots in via the soccer branch (handled earlier
|
||||
// for the soccer sport — the venue is part of the cascade).
|
||||
} catch (err) {
|
||||
console.warn('[computeFeatures] weather lookup skipped:', err.message);
|
||||
}
|
||||
|
||||
const trap = await safeGetTrap({
|
||||
playerName: player,
|
||||
statType,
|
||||
|
||||
Reference in New Issue
Block a user