Session 7j: Soccer intelligence - 9 leagues, 11 signals, 6 traps, poller, prefetch, 131 new tests (1173 total)

This commit is contained in:
Kev
2026-06-10 14:50:13 -04:00
parent b9084408bf
commit ad5ea8d5a8
28 changed files with 3175 additions and 49 deletions
+89 -1
View File
@@ -4,7 +4,95 @@
2026-06-10
## Current Phase
SHIP BUILD v7.1 — Stripe Route + Webhook Verification (Session 7i)
SHIP BUILD v7.2 — Soccer Intelligence + World Cup 2026 (Session 7j)
## Session 7j (2026-06-10) — SHIPPED
Permanent soccer sport vertical, launching with FIFA World Cup 2026
(opens June 11). League-agnostic architecture supports WC, EPL, La Liga,
Bundesliga, Serie A, Ligue 1, UCL, MLS, Liga MX from the same code paths.
### Files created
- `src/data/worldcup2026.js` — 16 venues + altitudes + climate, CONCACAF
+ CONMEBOL teams, penalty/corner/free-kick takers (top 25 teams),
tournament players (≥3 career WC goals). All frozen. Helpers:
`isPenaltyTaker`, `isCornerTaker`, `isFreeKickTaker`,
`getTournamentHistory`, `isHomeContinent`, `getVenue`, `altitudeImpact`.
- `src/services/adapters/footballDataAdapter.js` — football-data.org v4
REST adapter. 8/min token bucket (2-req safety margin vs the 10/min
upstream cap). Tier-matched Redis TTLs (fixtures 6h, standings 12h,
squads 24h, scorers 6h). Stale-while-revalidate fallback when the
bucket is drained or the API 5xx's. Returns null when no API key —
callers degrade gracefully.
- `src/services/intelligence/soccerFeatureExtractor.js` — reads from
prefetch-populated Redis cache (NEVER hits external APIs on the
user request path). Builds the engine1 feature vector + a soccer
overlay (goals_per_90, xG, penalty/corner/FK role, altitude,
referee, tournament history, rest_days).
- `poller/soccer.js` — league-agnostic fixture poller. WC pulls from
the rezarahiminia/worldcup2026 OSS API (no rate limit) and falls
back to football-data.org. Other leagues use the adapter directly.
Writes `soccer:nextmatch:{team}` (24h TTL) + `soccer:lastfixture:{team}`
(7d TTL) per fixture. Self-rescheduling: 5-min ticks during live
matches, 30-min otherwise. PM2-managed.
- `scripts/soccer-data-prefetch.js` — daily batch job. Pulls standings
+ scorers per configured league, computes per-team defensive
aggregate (`goals_conceded_per_game`, `defensive_rank_norm` on a 0..1
scale that slots into engine1's `opp_rank_stat`) and per-player
per-90 rates. Writes `soccer:teamdefense:{league}:{team}` and
`soccer:player:{normalizedName}`. `--leagues=WC,PL --dry-run` flags
supported. xG fields left null on Day 1 (soccerdata-Python bridge is
a follow-up; engine handles nulls gracefully).
- `tests/unit/worldcup2026.test.js` (20 tests)
- `tests/unit/footballDataAdapter.test.js` (15 tests)
- `tests/unit/soccerFeatureExtractor.test.js` (17 tests)
- `tests/unit/trapDetectionSoccer.test.js` (21 tests)
- `tests/unit/computeFeaturesSoccerBranch.test.js` (4 tests)
- `tests/unit/analyzeViaEngine1Soccer.test.js` (8 tests)
- `tests/unit/soccerPoller.test.js` (22 tests)
- `tests/unit/soccerDataPrefetch.test.js` (14 tests)
- `tests/integration/oddsSoccer.test.js` (6 tests)
### Files modified
- `src/utils/oddsNormalizer.js``MARKET_MAP` gains 10 soccer market
keys (`player_goals`, `player_shots_on_target`, etc → `goals`,
`shots_on_target`, etc). Existing NBA mappings untouched.
- `src/routes/analyze.js`, `src/routes/scan.js``VALID_STAT_TYPES`
set extended with 10 soccer stat types. `'assists'` is shared with
NBA; `sport` field discriminates downstream.
- `src/routes/odds.js` — new `GET /api/odds/soccer/:league` route.
Validates league against `SOCCER_SPORT_KEYS` (9 leagues), surfaces
405 valid-list hint on miss.
- `src/services/oddsService.js``SPORT_KEYS` gains 9 soccer entries
mapping `soccer_wc``soccer_fifa_world_cup`, `soccer_epl`
`soccer_epl`, etc. `SOCCER_SPORT_KEYS` exported as a frozen list.
- `src/services/intelligence/computeFeatures.js` — `sport ∈
{'soccer','football'}` dispatches to `extractSoccerFeatures`. NBA
path unchanged.
- `src/services/intelligence/trapDetection.js` — six soccer signals
(xg_regression, altitude_risk, rotation_risk, minute_discount,
referee_card_bias [positive — excluded from composite],
strong_defense). `getTrapScore` branches on `input.sport`.
- `src/services/intelligence/analyzeViaEngine1.js` — soccer reasoning
branch (`buildSoccerReasoningLines`). Uses "matches" not "games",
surfaces xG / penalty taker / altitude / referee / minutes / WC
pedigree. NBA-specific sentences (back-to-back, injury report)
guarded by `!isSoccer`.
- `poller/ecosystem.config.js` — `poller-soccer` PM2 app added. Same
restart policy as box-score pollers; `SOCCER_LEAGUES` env wired.
- `.env.example` — soccer block (`FOOTBALL_DATA_API_KEY`,
`SOCCER_LEAGUES`, `WORLDCUP_API_URL`, `RAPID_API_KEY`).
- `docs/SYSTEM-MANIFEST.md` — `/api/odds/soccer/:league` row in §2,
Soccer env block in §3, soccer poller in poller-set, four new
external API rows in §6, `[ARCH-3]` soccer-pipeline note in §8.
### Quality gates (all green)
- `npm test`: **1173 / 1173 passing** (1042 baseline + 131 new soccer
tests across 9 new suites), 91 suites, 0 failures
- `web/npm run build`: clean
- License audit: only permissive third-party licenses
---
## Session 7i (2026-06-10) — SHIPPED