Session 7j: Soccer intelligence - 9 leagues, 11 signals, 6 traps, poller, prefetch, 131 new tests (1173 total)
This commit is contained in:
+89
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user