Session 16: Live hero prop, sport-specific markets fix, soccer weather, Sentry CSP (1429 tests)
This commit is contained in:
+107
-1
@@ -4,7 +4,113 @@
|
||||
2026-06-11
|
||||
|
||||
## Current Phase
|
||||
SHIP BUILD v15.0 — Intelligence hardening + platform correctness (Session 15)
|
||||
SHIP BUILD v16.0 — Live hero prop + sport-scoped markets + launch polish (Session 16)
|
||||
|
||||
## Session 16 (2026-06-11) — SHIPPED
|
||||
|
||||
### Phase 1 — Sport-specific market map
|
||||
|
||||
`src/services/oddsService.js` now scopes the markets-list parameter
|
||||
to the requested sport. Previously every odds-api request sent
|
||||
`ALL_MARKETS` (the union of every sport's markets), which the
|
||||
upstream 422'd on because soccer markets (`player_goals`,
|
||||
`player_shots_on_target`, etc.) aren't valid for basketball
|
||||
endpoints. Production briefly worked around this with a runtime
|
||||
axios interceptor injected via
|
||||
`NODE_OPTIONS=--require /app/data/patch.js`.
|
||||
|
||||
This session retires that hack at the code layer:
|
||||
- New `SPORT_MARKETS` map alongside `SPORT_KEYS` — separate lists
|
||||
per sport, all frozen with `Object.freeze`. NBA + NCAAB share
|
||||
basketball markets; WNBA is basketball minus PRA (odds-api
|
||||
doesn't carry that for WNBA); MLB sends batter + pitcher markets;
|
||||
every soccer league shares the soccer set.
|
||||
- `fetchEventOddsFromApi(sportKey, eventId, apiKey, sport)` —
|
||||
third arg added; reads `getMarketsForSport(sport)` instead of
|
||||
the union. Backwards-compatible: omitted sport falls back to
|
||||
NBA (safe default).
|
||||
- `fetchAllOdds(sport, apiKey)` — already had the local sport key;
|
||||
now passes it through.
|
||||
|
||||
**Coolify follow-up**: after this deploy, the operator can drop
|
||||
`NODE_OPTIONS=--require /app/data/patch.js` from the web service
|
||||
env and delete `/app/data/patch.js`. The runtime patch is now
|
||||
dead code.
|
||||
|
||||
### Phase 2 — Live hero prop
|
||||
|
||||
`web/src/app/api/hero-prop/route.ts` (new) — picks one fresh real
|
||||
prop from today's NBA → WNBA → MLB cascade and grades it. Two-stage
|
||||
flow: GET `/api/odds/{sport}` → POST `/api/analyze/prop`. Both
|
||||
calls share a 6s AbortController timeout. Server-side cached for
|
||||
15 minutes via `Cache-Control: s-maxage=900`. Falls back to a
|
||||
static Jokic example (`isStatic: true`) when every sport is empty
|
||||
so the landing page never blanks out.
|
||||
|
||||
`web/src/components/LiveHeroProp.tsx` (new) — replaces the
|
||||
hard-coded `FloatingDemoCard` inside `Hero.tsx`. Renders the live
|
||||
prop with:
|
||||
- "LIVE" badge with a pulsing green dot
|
||||
- Sport-colored category tag (NBA red, WNBA orange, MLB blue, soccer green)
|
||||
- Player name + line + projection + edge **visible** (hook)
|
||||
- Grade letter + confidence **visible** via GradePill (proof)
|
||||
- Reasoning section **blurred** with backdrop `blur(4px)`, a
|
||||
scan-line gradient (`repeating-linear-gradient`), a bottom-fade
|
||||
mask, and a "CLASSIFIED · Sign up to unlock" label (paywall)
|
||||
- Single CTA: "Sign up to read the full analysis →"
|
||||
|
||||
While loading OR when the API returns `isStatic: true`, renders
|
||||
the original Jokic mockup byte-for-byte. No flash-of-blank-card.
|
||||
|
||||
`Hero.tsx` — old `FloatingDemoCard`, `Stat`, and `row` constant
|
||||
deleted. `GradePill` import moved into `LiveHeroProp`.
|
||||
|
||||
### Phase 3 — Soccer weather
|
||||
|
||||
`soccerFeatureExtractor.js` now calls `weatherService.getWeather()`
|
||||
for outdoor WC venues after resolving the venue. Dome venues skip
|
||||
the fetch. Unknown venues skip silently. New feature fields:
|
||||
`weather_temp_f`, `weather_wind_mph`, `weather_wind_dir`,
|
||||
`weather_precip_mm`. All null when skipped/failed.
|
||||
|
||||
### Phase 4/5 — OG tags + CSP (mostly already done)
|
||||
|
||||
OG meta + Twitter card + `og-image.png` were all wired in Session 9.
|
||||
Existing CSP in `next.config.ts` was comprehensive. Session 16 added:
|
||||
- `https://browser.sentry-cdn.com` to `script-src` (Sentry SDK)
|
||||
- `https://*.sentry.io` and `https://*.ingest.sentry.io` to
|
||||
`connect-src` (event ingestion). Without these the browser
|
||||
Sentry client silently dropped events.
|
||||
|
||||
### Tests added (Session 16)
|
||||
| Suite | Tests |
|
||||
|----------------------------------------|-------|
|
||||
| `tests/unit/sportMarkets.test.js` | 16 |
|
||||
| `tests/unit/soccerWeather.test.js` | 7 |
|
||||
| **Session 16 total** | **23**|
|
||||
|
||||
### Quality gates
|
||||
- `npm test`: **1429 / 1429 passing** (1405 + 24), 110 suites, 0 regressions
|
||||
- `web/npm run build`: clean
|
||||
- License audit: third-party deps remain permissive
|
||||
|
||||
### Honest gaps
|
||||
- `LiveHeroProp`'s glitch effect (scan lines + blur + fade) renders
|
||||
only in a browser. Build verified. Deploy smoke-test recommended.
|
||||
- Hero endpoint depends on `/api/odds/{sport}` returning populated
|
||||
`props`. If upstream odds-api is rate-limited or proxies aren't
|
||||
reaching Express, the static fallback fires — cold visitors see
|
||||
the Jokic mockup, not live data.
|
||||
- Sentry CSP entries added but require redeploy to take effect.
|
||||
Until then, the browser SDK silently drops events.
|
||||
|
||||
### Coolify follow-ups
|
||||
1. **Drop the patch.js workaround**: remove
|
||||
`NODE_OPTIONS=--require /app/data/patch.js` from the web
|
||||
service env. Code-layer fix in Session 16 makes the runtime
|
||||
patch obsolete.
|
||||
|
||||
---
|
||||
|
||||
## Session 15 (2026-06-11) — SHIPPED
|
||||
|
||||
|
||||
Reference in New Issue
Block a user