What we shipped, when.
Every public change to Line Gap. We update this on every deploy that touches the product. Want the deep-dive? See the Research posts.
- fix
Fix: duplicate Kalshi rows and inflated alt-line EV
Two issues surfaced once Kalshi started flowing into the main OddsTable. Both shipped within the same day; the v1.2.0 launch entry is unchanged.
Stale snapshots stacking as duplicates
All three poll paths (retail / Kalshi / Pinnacle) inserted a new
sportsbook_oddsrow every time a price changed but never marked the previous snapshotis_active = false. The dedup-on-insert guard skipped same-price re-INSERTs but did nothing about price moves. Over a day of polling, the same(player, prop, line, sportsbook)accumulated 4–6 active rows — the Deandre Ayton 19.5-points line was the visible symptom (six near-identical rows ranking high on EV).Affected counts at the time of fix:
- 1,388 stale active Kalshi rows
- 1,233 stale active retail rows (from the fetch-data path)
- 390 stale active Pinnacle rows
The fix:
- One-time SQL cleanup deactivated all 3,011 stale rows.
- New
deactivate_superseded_odds()Postgres function keeps only the latestis_active = truerow per dedup group; idempotent and scoped bysportsbook_key. - Every poll route (
poll-odds,pollKalshiNba,pollKalshiNbaGames,pollPinnacleNba) now calls the function via.rpc()after its batch insert.
Alt-line junk EVs
Kalshi exposes the full strike ladder for every player prop (e.g. Ayton points at 5.5 / 9.5 / 14.5 / 19.5 / 24.5). Our projection model is well-calibrated near a player's seasonal mean; at tail strikes the StatProb stays in the 10–15% range even when the market's no-vig fair prob has collapsed to 2–4%. The math then displays "+1500% EV" — technically correct, structurally nonsense.
The OddsTable now drops any row whose projection-implied probability diverges from the market-implied probability by more than 25 percentage points. Retail books don't expose alt ladders, so in practice this only filters Kalshi alt-line rows; retail and Pinnacle main-line rows are unaffected.
- v1.2.0featureimprovementinfrastructure
Kalshi prediction-market lines, end-to-end
The biggest release since we shipped CLV tracking. Kalshi's prediction-market prices are now a first-class part of the Line Gap surface — visible alongside DraftKings, FanDuel, BetMGM and the rest, with their no-vig math made explicit.
Game-level Kalshi NBA markets
- New polling cron at
:04past the hour pulls Kalshi'sKXNBAGAME(moneyline),KXNBASPREAD, andKXNBATOTALmarkets every 5 minutes. - Markets land in
sportsbook_oddsas Yes-canonical rows (player_id NULL,under_odds NULL) so they sit beside retail rows without duplicating storage. - A closest-to-retail strike selector picks one row per
(game, market_type, side)so the comparison surface stays clean even when Kalshi exposes 10+ alternate strikes per game. - A both-sides-quoted gate (D-06) rejects phantom one-sided quotes before they pollute the table.
Kalshi in the main book list
- Open any NBA player prop on
/oddsand Kalshi appears as a regular book row alongside DraftKings, FanDuel and the rest — with a small PM badge to mark its no-vig pricing. - Kalshi's filter chip is now in the Sportsbooks dropdown.
- The per-prop expansion table ranks Kalshi by EV against retail consensus, so when Kalshi prices a prop dramatically differently from the books, you see the divergence inline.
Best Bets gets a "Kalshi edge" hint
- Best Bets still anchors on the highest-EV retail line — Kalshi's unvigged prices would otherwise hijack every callout — but when Kalshi's fair probability diverges from retail consensus by ≥2 percentage points, the Best Bet callout now shows whether Kalshi agrees or disagrees with the retail pick, and by how much.
- This is the "sharp money sees something the books don't" signal you used to have to compute yourself.
Daily Games card expand
- Click "Show prediction-market lines" on any NBA game card on the dashboard and you'll see Kalshi's moneyline / spread / total for that game, snapshot-timestamped to the latest poll.
- Honest scope: retail moneyline/spread/total ingest isn't built yet, so this is currently a Kalshi-only view — full multi-book game-line comparison ships in a future phase.
Projection enrichment for Kalshi rows
- The hourly enrich-odds cron now stamps StatProb, Confidence, and EV onto Kalshi player-prop rows using the same projection math retail rows get. 77% of active Kalshi rows now carry full projection columns; the rest have no projection coverage (retired players, future games not yet projected).
Schema & security
- Forward-only migration widens the
sportsbook_oddsCHECK constraints to cover the new game-linemarket_typeandprop_typevalues across NBA + NFL + NHL. sportsbook_odds.player_idis now nullable (Phase 7 game-line rows persist withplayer_id NULLper the Yes-canonical convention).- Resolved every active finding in the Supabase Security Advisor: RLS enabled on
daily_odds_summary, mutablesearch_pathpinned on three SQL functions, and theline_movement_summarymatview removed from the anonymous PostgREST surface.
EV calculation hardened on the alt-strike ladder
- Kalshi exposes every strike (e.g. 5.5 / 9.5 / 14.5 / 19.5 / 24.5 points for the same player), and retail books only post lines near a player's seasonal mean. That asymmetry surfaced a long-standing tail-bias in the "simple" projection model — at extreme strikes it would say "35% over 19.5" for a 10-ppg guard while the market priced the same event at 2%, producing +1690% EV displays that looked like impossible edges.
- StatProb + EV now read from the full projection (game-context-aware variance), which is well-calibrated on tails. Retail rows are unchanged in the meat of the distribution; Kalshi extreme-strike rows now show realistic single-digit probabilities and small EVs.
- Existing enriched rows in
sportsbook_oddswere updated in-place; future rows enrich correctly via the cron.
Under the hood
- 17 commits, 305 tests passing, no breaking changes to any retail-only surface (Best Bets, cross-book detection, sharp signals).
- Documented two open follow-ups: pair
spread_home/spread_awayto a single strike, and backfill retail game-line ingest so the DailyGames expand can become a true multi-book comparison.
- New polling cron at