NBA Feed
Your teams' news and games, with scores hidden until you reveal them.
Over/under probabilities for DraftKings Pick 6 player props, from recent games, matchups and injury news.
Sports & fitnessTypeScript · CSSGitHub @rp8g7mjrr5-designOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Tracks DraftKings Pick 6 over/under player props for NBA and NFL and estimates the chance each one hits, using recent games, the opponent matchup, and injury news.
npm install
npm run dev # http://localhost:3000
npm test # model tests
Out of the box it pulls live NBA and NFL stats and injuries from ESPN's public feeds
(no key needed). Paste the day's Pick 6 lines into the box on the board and it looks up
each player, their next opponent, and scores the line. Set DATA_SOURCE=sample to see the
demo board with made-up lines instead.
ODDS_API_KEY set the line starts at
Pick 6's posted line (or the sportsbook median when Pick 6 hasn't posted it); otherwise at the
season average. Odds update live as the line moves.
To save Odds API credits, only the most-played stats are fetched (NBA points, rebounds, assists,
threes and PRA, for example), only for games that haven't started, and each game's lines are cached
for everyone for six hours. Fetching pauses before the monthly credits run out.lib/parseLines.ts).Every 30 minutes at most (lib/slateData.ts, kept fresh by the alerts cron), today's posted lines are scored once
and saved. That scan only uses lines already cached, plus one game's worth a day if nobody has opened
any, so it costs almost no Odds API credits. Built on it:
lib/potd.ts): the model's best line, graded in public.lib/edges.ts): soft lines, where the books' price at the Pick 6 line favors
one side by 57%+ (Pro), and injury ripples, where a teammate's usage bump hasn't moved their line (free).
Both can push to phones, a few a day at most.lib/lock.ts, lib/lockData.ts): a free one-pick-a-day streak game with badges and a
streak leaderboard.lib/pairs.ts), hot spots (lib/hotspots.ts) and crowd vs model
(lib/crowd.ts, counted from saved slips).lib/ask.ts): plain-English questions answered from the model's numbers, no AI service.Also: post-game recap cards (lib/recap.ts), the bankroll coach (lib/bankroll.ts) and weekly crew
contests and head-to-heads (lib/contest.ts).
KV_REST_API_URL and
KV_REST_API_TOKEN for you. Redeploy.CRON_SECRET env var (any long random string) so only Vercel's cron can
hit the refresh endpoint.vercel.json schedules /api/cron/refresh once a day at 13:00 UTC (Hobby allows one run a day).
It rebuilds team defense numbers from the season's box scores and saves them to Redis. Without
Redis the app still works: it computes a lighter version on the first request and caches it.
lib/espn.ts)| What | ESPN feed | Notes |
|---|---|---|
| Player lookup | site.web.api.espn.com/apis/common/v3/search | Exact name match first, team as a tiebreaker |
| Game logs | .../athletes/{id}/gamelog | Regular season and playoffs; tops up with last season early in the year |
| Injuries | site.api.espn.com/.../injuries | Out / IR / suspended → "ruled out"; questionable, doubtful, day-to-day → "questionable" |
| Upcoming games | .../scoreboard | Next three days (NBA), rest of this week plus next week (NFL) |
| Team players | .../teams/{id}/athletes/statistics | Season per-game averages, most minutes first; roster as a fallback |
| Next opponent | .../scoreboard | Today through two days out (NBA), this week and next (NFL) |
| Team defense | NBA scoreboards, NFL game summaries | What each team allows vs league average, shrunk toward average by games played |
These feeds are unofficial and can change without notice; parsers skip fields they don't find
rather than crash, and npm test checks them against saved real responses in lib/fixtures/.
Known gaps: minutes-limit news doesn't apply to NFL (snap counts come from nflverse, not the game logs); teammate-out usage bumps still need a manual news item.
lib/model.ts)The slip's chance allows for picks that move together (lib/correlation.ts): a QB and his receiver, one
player's points and PRA, two skaters on the same team, a pitcher's hits allowed and the hitters facing him.
Each linked pair gets an estimated correlation (flipped when one leg is an under), and the slip is simulated
with a Gaussian copula; unrelated legs still just multiply. The slip says which picks are linked and whether
that helps or hurts. The correlations are rough estimates, not yet fitted to graded picks.
app/ pages and the /api/props routecomponents/ board, slip, and tracker UIlib/model.ts probability model, lib/providers.ts data sources,
lib/sampleData.ts sample lines/stats/newslib/espn.ts ESPN feeds, lib/parseLines.ts Pick 6 paste parser,
lib/refresh.ts defense snapshot + cron job, lib/db.ts Redis clientlib/odds.ts)The same Odds API call that finds Pick 6's line also returns the sportsbooks' prices, so the market read costs
no extra credits (Pinnacle and BetOnline are in the bookmaker list; up to 10 books cost the same as one region).
The pick card shows the books' no-vig chance next to the model's, says when Pick 6's line sits off the books'
(books at 27.5 and Pick 6 at 25.5 makes More the easier side), and shows how far the line has moved since we
first saw it (openers are kept for five days under odds:open:{eventId}). This is the props version of
"public vs sharp money": betting splits only exist for game lines, and the sharp books' price is the signal
that matters for a player prop.
Pasted lines get the same market read (lib/gameContext.ts): each line is matched to its game,
and the game's cached prop prices are used. Lines nobody has opened yet are fetched only for Pro, as on the
game picker.
lib/environment.ts)Spreads and totals for a sport's whole slate come from one Odds API call (two credits, cached six hours and shared by everyone, so it isn't a Pro feature). From them each team gets an implied total, measured against the slate's average game total (or a typical total when fewer than four games are priced). The model scales each stat by that, with modest weights: scoring stats follow the team's implied total, rebounds follow the game total, and goalie saves and pitcher stats follow what the opponent is expected to score. NBA starters lose up to 6% when the spread is 10 or more (blowout risk), and in the NFL underdogs lean toward passing and favorites toward running, up to 6%. The total swing is capped at 15%.
lib/nflverse.ts, lib/nflData.ts)nflverse publishes free weekly player stats and snap counts as CSVs on GitHub. The daily cron (or the first request after 36 hours) downloads this season's files (about 4 MB), boils them down to a ~200 KB summary and saves it. From it the model gets:
If nflverse can't be reached the model runs without these, as before.
lib/espn.ts)lib/usage.ts, lib/usageData.ts)When ESPN's injury report rules out a rotation player (top 8 by minutes, at least two games this season), each teammate's projection gets a bump for the shots, rebounds and assists left behind. Two reads, blended:
Each teammate's bump is capped at +20% (and −15%), all of them together at +35%. Players who have already been out for this player's last five games are skipped, since the recent average already reflects it. The pasted board and the game picker both use it; the sportsbooks' price, when there is one, still pulls the final number.
FBS football and Division I men's basketball come from the same ESPN feeds as the pros (game logs, schedules, team stats, injuries) and use the NFL and NBA stat menus. A pasted line reads as NFL or NBA from its stat; a player ESPN doesn't find there is looked up in college next. What's different:
CBB:DUKE so it never mixes with a pro team's abbreviation (MIA, HOU...).lib/ufc.ts, lib/ufcData.ts)Significant strikes and takedowns. A fighter's count is how fast they land times how long the fight lasts, so the projection is built that way instead of averaging fights that ran different lengths:
ufc:fights); each fighter's stats page
gives per-fight strikes and takedowns. The picker lists the next card, one fighter per side.lib/mlb.ts, lib/mlbData.ts)Each piece is a multiplier on the player's average, shrunk toward league average by the sample behind it, and listed in the reasons:
PARKS), measured against the mix the
player's average already reflects (about half at their home park).Early in a season, last season's splits and team batting fill in at half weight. All of it comes from ESPN except the weather; the matchup total is capped at −20%/+25%.
lib/splits.ts)How often the player cleared today's line in games like today's: last 5, this season, against this opponent (last season included), home or road, and the second night of a back-to-back (NBA and NHL). A split that goes the same way at least 75% of the time over 4+ games is ringed as a pattern. Splits are shown, not fed to the model: the samples are small and the model already weights recent form.
Saved picks grade themselves: after each game the tracker looks up the player's box score and marks
hit, miss or push (a player who doesn't play counts as a push). Tap a result to override. The
model report card buckets graded picks by the model's chance so you can see whether 60% picks
really hit about 60% of the time. /setup shows which optional keys the deployment has.
Public report card (/accuracy, lib/accuracy.ts): every saved pick across all users, graded from box
scores, bucketed by the model's chance, with a Brier score and per-sport hit rates. Same counting rules as the
leaderboard, and a pick many people played counts once. Rebuilt at most hourly; linked from the footer.
Closing-line check (lib/clv.ts): a pick saves the sportsbooks' price it had when picked. Every fresh odds
fetch keeps the latest price per prop for its game day (odds:close:{sport}:{date}), and once the game starts the
tracker pulls the last one seen before it. Each pick shows how its chance moved, with a beat-the-close summary.
Self-tuning (lib/calibration.ts): the daily refresh fits a two-number recalibration per sport (how sure the
model should be, and any lean to overs or unders) to every graded pick, held at "no change" until a couple of hundred
picks back it. The board and picker apply it with a reason line, picks save the untuned chance so it never learns
from itself, and /accuracy shows what it's doing.
Alerts (lib/alerts.ts, lib/alertsData.ts, public/sw.js): phone notifications (Web Push) when a player
on an open pick is ruled out or questionable, or the books move the pick's chance 5+ points. People turn them on
from the profile or tracker; on iPhone the site has to be added to the Home Screen first. Setup:
npx web-push generate-vapid-keys and set NEXT_PUBLIC_VAPID_PUBLIC_KEY and
VAPID_PRIVATE_KEY on Vercel (optionally VAPID_SUBJECT, a mailto: address). Redeploy.SITE_URL (your site's address) and CRON_SECRET (same as on Vercel).
.github/workflows/alerts.yml then calls /api/cron/alerts every 30 minutes.Live sweat (lib/live.ts, /api/live): while a game on an open pick is on, the tracker's "Live now" section
reads ESPN's in-game box score every 30 seconds (only while the page is visible) and shows the stat so far against
the line, the game clock and score, and a pace projection once 15% of the game is played. Overs past the line show
as cashed, unders past it as busted. Total bases and UFC aren't tracked live (the box score doesn't add them up).
Pick of the day (lib/potd.ts, lib/potdData.ts): once lines are up for today's games, the model scores them
and locks in its strongest clean pick for the day (8+ games of sample, no injury flag, not low confidence, and the
books not leaning the other way). It shows at the top of the Board with an Add to slip button, gets graded from
the box score after the game, and its record shows on the card. It uses lines already cached from people opening
games; if nobody has yet, it buys one game's lines once a day (a few Odds API credits). The alerts cron warms it,
and it's re-tried every 20 minutes until there's a pick.
Share a slip (lib/share.ts, /s/{id}): the slip and each saved slip have a Share button that makes a short
link (kept a week) with a link-preview image of the picks. Anyone opening it can tail the picks still open.
lib/accounts.ts, components/Account.tsx)Sign-in is handled by Clerk: Google, Apple or an email code, no passwords to store.
An account keeps the user's Pro key and saved slips (account:{userId}, slips:user:{userId}), so both
follow them to any device. With accounts on, buying Pro or redeeming a lifetime code needs sign-in, and a
Pro key bought earlier on a device is linked to the account the first time that device signs in.
Turn it on by adding Clerk from the Vercel Marketplace (it sets NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and
CLERK_SECRET_KEY), then pick sign-in methods in Clerk's dashboard. Without those keys the site runs
signed out with Pro keys and sync codes, exactly as before.
Username and profile: a new account picks a username before anything else (live availability check). The
profile (/profile, from the avatar menu) shows record, hit rate against the model, slip profit, streaks, recent
form, splits by sport, prop and side, closing-line results, leaderboard rank and alert settings.
Leaderboard (/leaderboard, lib/leaderboard.ts): everyone with a username is on it unless they hide. It ranks by
hit rate once someone has 10 counted picks. Only picks graded from box scores and saved before their game
started count, so hand-marked wins don't. The board is rebuilt at most every 10 minutes.
Crews (/crews, lib/crews.ts): private groups joined with an invite link (/join/{code}). Members see a
crew leaderboard (same counting rules as the global one) and each other's slips from the last 7 days, without
entry amounts, and can tail a slip: its picks whose games haven't started are copied into their own slip
(lib/tail.ts). Crews need accounts and a username; up to 50 members a crew and 10 crews a person. The owner can
remove members or reset the invite link; when the owner leaves, the longest-standing member takes over.
Invite friends (lib/referrals.ts, /api/referral): everyone with a username gets an invite link
(/?ref=username) on their profile, and opening someone's shared slip counts as their invite too. When a new
account (under three days old) signs up from one, both get a free week of Pro: a trial pass that stacks on a running
week and leaves a paid plan alone. One claim per account and up to 12 rewarded invites per person. Needs accounts and
Pro set up; trial weeks can still buy a plan.
First visit: a three-step walkthrough at the bottom of the Board follows what the person does (tap a game, tap a player and pick a side, save the slip) and pulses what to tap next. It doesn't show for anyone with saved slips.
Admin (/admin, lib/admin.ts): accounts whose verified email is in ADMIN_EMAILS get an Admin tab.
It shows every lifetime code and whether it's been used, makes new codes (stored in the database, so they
work without a redeploy), revokes a used code's Pro, and shows Pro member counts and Odds API credits left.
lib/pro.ts)Pro unlocks auto-filled lines, the best-slip builder, tracker insights, CSV export and the report card. There are no accounts: paying (Stripe Checkout)
or redeeming a lifetime code gives the browser a Pro key like LEG-ABCD-EFGH-JKLM, which works on
any device it's typed into on /pro. Subscription keys are re-checked with Stripe every few hours,
so a cancelled plan stops working at the end of its period. Nothing is locked until
STRIPE_SECRET_KEY or LIFETIME_CODES is set.
Setting it up:
price_...) into STRIPE_PRICE_MONTHLY and STRIPE_PRICE_YEARLY.STRIPE_SECRET_KEY and set the two NEXT_PUBLIC_PRO_*_LABELs.LIFETIME_CODES, comma separated, or make them on /admin. Each works once.PRO_TRIAL_DAYS (default 7, 0 turns it off). Stripe takes the card at checkout
and charges when the trial ends. It's once per signed-in account, and accounts that have paid before don't get one.Selling points that work for free: everyone sees one soft line a day in full (the rest are blurred), and every soft line
we flag is logged and graded from the box score, so the Board and /pro show the real 30-day record once 10 have graded.