Session 14: Africa checkout, Tank01 NBA/MLB wiring, WNBA+MLB odds proxies, OAuth icons, loading skeletons (1330 tests)

This commit is contained in:
Kev
2026-06-11 10:06:49 -04:00
parent 10159209fa
commit f5d79cf70d
22 changed files with 979 additions and 27 deletions
@@ -32,6 +32,11 @@ 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');
// Session 14 — Tank01 augmentor. Reads cache keys the Tank01
// adapters write; no network from this path. Daily prefetch (future)
// populates the cache. Until that lands, the augmentor returns
// empty objects and the existing ESPN-derived features stand alone.
const tank01Augment = require('./tank01Augment');
const HTTP_TIMEOUT_MS = 8_000;
@@ -188,6 +193,37 @@ async function computeFeaturesForProp(rawProp = {}) {
errors.push('no_features_computed');
}
// Session 14 — Tank01 augmentation. Sport-specific. Both calls are
// cache-only (no network), Promise.allSettled-style isolated so a
// Redis hiccup on the Tank01 read doesn't fail the whole grade.
// The `t01_*` fields land alongside the ESPN-derived features;
// grading + reasoning + trap detection read them when present and
// ignore them when absent.
const ymd = new Date().toISOString().slice(0, 10).replace(/-/g, '');
try {
if (sport === 'nba') {
const aug = await tank01Augment.augmentNbaFeatures({
gameId: game?.gameId ?? null,
playerName: player,
ymd,
});
Object.assign(features, aug);
} else if (sport === 'mlb') {
const aug = await tank01Augment.augmentMlbFeatures({
gameId: game?.gameId ?? null,
batterName: player,
// batterId/pitcherId/pitcherName not yet plumbed through
// computeFeatures — the augmentor returns name-only markers
// when IDs are absent.
ymd,
});
Object.assign(features, aug);
}
} catch (err) {
// Never let augmentation failure poison the grade.
console.warn('[computeFeatures] Tank01 augmentation skipped:', err.message);
}
const trap = await safeGetTrap({
playerName: player,
statType,