{"id":50919063,"url":"https://github.com/kyleseneker/cbb-hub","last_synced_at":"2026-06-16T18:01:25.989Z","repository":{"id":358707244,"uuid":"1242682751","full_name":"kyleseneker/cbb-hub","owner":"kyleseneker","description":"Open-source D1 college baseball landscape: live RPI, regional host projection, 64-team bracket projection, what-if result simulator. Python engine + TypeScript port + Next.js + GitHub Actions.","archived":false,"fork":false,"pushed_at":"2026-05-29T16:31:29.000Z","size":3323,"stargazers_count":0,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T17:05:35.542Z","etag":null,"topics":["bracketology","college-baseball","d1-baseball","ncaa","ncaa-baseball","nextjs","open-source","python","rpi","sports-analytics","typescript","vercel"],"latest_commit_sha":null,"homepage":"https://cbb-hub.vercel.app","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kyleseneker.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-18T16:47:52.000Z","updated_at":"2026-05-29T16:31:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kyleseneker/cbb-hub","commit_stats":null,"previous_names":["kyleseneker/cbb-hub"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kyleseneker/cbb-hub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleseneker%2Fcbb-hub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleseneker%2Fcbb-hub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleseneker%2Fcbb-hub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleseneker%2Fcbb-hub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyleseneker","download_url":"https://codeload.github.com/kyleseneker/cbb-hub/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleseneker%2Fcbb-hub/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34417416,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bracketology","college-baseball","d1-baseball","ncaa","ncaa-baseball","nextjs","open-source","python","rpi","sports-analytics","typescript","vercel"],"created_at":"2026-06-16T18:01:24.874Z","updated_at":"2026-06-16T18:01:25.980Z","avatar_url":"https://github.com/kyleseneker.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Field of 64\n\n[![CI](https://github.com/kyleseneker/cbb-hub/actions/workflows/test.yml/badge.svg)](https://github.com/kyleseneker/cbb-hub/actions/workflows/test.yml)\n[![Scrape](https://github.com/kyleseneker/cbb-hub/actions/workflows/scrape-and-commit.yml/badge.svg)](https://github.com/kyleseneker/cbb-hub/actions/workflows/scrape-and-commit.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nA year-round site for NCAA Division I baseball. Scoreboard, standings, RPI,\nprojected hosts and bracket, and a scenario simulator.\n\nLive at [cbb-hub.vercel.app](https://cbb-hub.vercel.app).\n\n## What's here\n\nRankings use the NCAA's published RPI from\n[ncaa.com/rankings/baseball/d1/rpi](https://www.ncaa.com/rankings/baseball/d1/rpi).\nBase RPI is also computed locally (adjusted WP1 with 1.3 / 0.7 venue\nweighting, WP2 with self-game exclusion, OOWP-based WP3, D1-only) and\nshown next to the published rank as a transparency column. The simulator\non `/scenarios` and `/path` recomputes base RPI in the browser when you\nflip an upcoming game.\n\nA Supabase Edge Function pulls NCAA's contests GraphQL API on a\n10-minute cadence (pg_cron fires every minute; the function self-skips\nbetween runs), writes a snapshot to Postgres, and the Next.js front-end\nreads from the DB.\n\n## Pages\n\n| Path | What it shows |\n| --- | --- |\n| `/` | Yesterday's finals, today's slate, on-deck matchups; Top 25; phase-aware links. |\n| `/scoreboard` | D1-vs-D1 scoreboard, paginated by date. |\n| `/rankings` | Every D1 team, filterable by conference, with host (1-16) and at-large (17-64) pools highlighted. |\n| `/standings` | Conference standings by league. |\n| `/bubble` | Host line at #16 and at-large line at #64; last-four-in / first-four-out. |\n| `/bracket` | Projected field of 64 via S-curve into 16 regionals. |\n| `/conferences` | Per-conference averages, medians, projected hosts and bids. |\n| `/scenarios` | Pick winners for upcoming games and watch RPI re-rank. |\n| `/path` | Pick a team and a goal (host, field, top 25, custom rank); the solver finds the minimum games it takes. |\n| `/team/[slug]` | RPI components, record splits, quality wins, bad losses, full schedule. |\n| `/methodology` | Formula write-up and accuracy panel vs the published index. |\n| `/api/rankings` | JSON: canonical-ranked teams with computed RPI components. |\n\n## Local development\n\nSetup happens once; see [supabase/SETUP.md](supabase/SETUP.md) (create\nthe Supabase project, apply migrations, deploy the Edge Function, set\nenv vars). Then:\n\n```sh\ncd web\nnpm install\nnpm run dev\nnpm test\nnpm run build\n```\n\n`web/.env.local` needs `NEXT_PUBLIC_SUPABASE_URL` and\n`NEXT_PUBLIC_SUPABASE_ANON_KEY`.\n\nThe Python pipeline under `src/cbb_hub/` is the reference engine for\nthe RPI math. The parity test\n[web/lib/__tests__/parity.test.ts](web/lib/__tests__/parity.test.ts)\nruns the TypeScript engine against a frozen Python snapshot to catch\ndrift. To re-run the Python pipeline directly:\n\n```sh\nPYTHONPATH=src python3 -m cbb_hub scrape-ncaa --year 2026 \\\n    --seed data/season-2026.json --output data/season-2026.json\nPYTHONPATH=src python3 -m cbb_hub rank data/season-2026.json --top 25\nPYTHONPATH=src python3 -m cbb_hub validate --season-file data/season-2026.json\npython3 -m unittest discover tests\n```\n\n## Architecture\n\n```\n   pg_cron (every 1 min, gated to 10 min cadence)\n          │\n          ▼\n   ┌─────────────────────────────┐         sdataprod.ncaa.com\n   │  Supabase Edge Function     │ ──────► GraphQL contests\n   │  supabase/functions/scrape  │\n   │                             │ ──────► ncaa.com/rankings\n   │  fetch, parse, compute RPI  │         (published RPI)\n   │  apply_scrape_snapshot()    │\n   └──────────────┬──────────────┘\n                  │ atomic write\n                  ▼\n   ┌─────────────────────────────┐\n   │  Supabase Postgres          │\n   │  teams, games, upcoming,    │\n   │  rankings, auto_bids,       │\n   │  scrape_runs                │\n   └──────────────┬──────────────┘\n                  │ anon SELECT (RLS)\n                  ▼\n   ┌─────────────────────────────┐\n   │  Next.js on Vercel          │\n   └─────────────────────────────┘\n```\n\nEach scrape writes a full snapshot via `apply_scrape_snapshot`; readers\nonly see the latest run with `status = 'success'`, so partial writes\naren't visible.\n\nThe RPI math lives in three places that have to stay in step:\n[src/cbb_hub/rpi.py](src/cbb_hub/rpi.py) (reference),\n[supabase/functions/_shared/rpi.ts](supabase/functions/_shared/rpi.ts)\n(Edge Function), and [web/lib/rpi.ts](web/lib/rpi.ts) (browser +\nNext.js server). Python is the spec; the parity test pins the\nTypeScript port to a frozen snapshot of Python's output. See\n[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full data-flow.\n\n## RPI formula\n\nNCAA D1 baseball, in effect since 2013:\n\n    RPI = 0.25 * WP1 + 0.50 * WP2 + 0.25 * WP3\n\n- WP1: adjusted team winning percentage. Each W/L is weighted by venue:\n\n  | Outcome | Weight |\n  | --- | --- |\n  | Road win | 1.3 |\n  | Home loss | 1.3 |\n  | Home win | 0.7 |\n  | Road loss | 0.7 |\n  | Neutral W or L | 1.0 |\n\n- WP2: average opponents' adjusted WP, with games against the target\n  team removed from each opponent's record.\n- WP3: average opponents' opponents' WP (unadjusted).\n\nOnly D1-vs-D1 games count toward base RPI.\n\n## Data source\n\nNCAA.com for both games and the published RPI:\n\n- Contests: the `sdataprod.ncaa.com` GraphQL endpoint returns every D1\n  contest for a given date with both teams, scores, `conferenceSeo`,\n  and a stable `contestId`. One HTTP call per calendar date.\n- Published RPI: `ncaa.com/rankings/baseball/d1/rpi`.\n\n`stats.ncaa.org` sits behind Akamai bot-detection that blocks\nserver-side fetches; the GraphQL and rankings paths above don't need\nauth.\n\nThe contests endpoint uses a persisted-query SHA256 hash embedded in\nthe scoreboard page's `drupalSettings`. The hash rotates whenever NCAA\nredeploys; the scraper re-reads it from the page each run.\n\n## Project layout\n\n```\nsrc/cbb_hub/\n  models.py          Game and Team dataclasses\n  rpi.py             RPI calculator (reference)\n  io.py              season JSON load/save\n  fetcher.py         rate-limited, disk-cached HTTP client\n  ncaa_rpi.py        NCAA RPI ranking page parser\n  ncaa_contests.py   NCAA GraphQL contests scraper\n  tournaments.py     conference tournament bracket types\n  score_patches.py   manual score corrections\n  cli.py             argparse entry point\ntests/\n  fixtures/          pinned HTML / JSON snapshots\nsupabase/\n  functions/scrape/  Edge Function (live scraper)\n  functions/_shared/ shared TS modules used by the function\n  migrations/        schema + RPC migrations\nweb/\n  app/               Next.js App Router pages\n  components/        UI primitives and page components\n  lib/               season-state, rpi, bracket, field, path\ndocs/                ARCHITECTURE.md and other contributor docs\ndata/                local dev fallback JSON\n.cache/              gitignored; HTML cached here for the Python CLI\n```\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyleseneker%2Fcbb-hub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyleseneker%2Fcbb-hub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyleseneker%2Fcbb-hub/lists"}