By Trevor White (@tdub29 on GitHub).
Baseball scouting, player evaluation, and MLB game modeling. The NCAA platform combines transfer-portal scouting, pitch-level models, hitter and pitcher Streamlit apps, and play-by-play reports used by the University of San Diego coaching staff. The MLB work covers Statcast pitch and contact models, run expectancy, recency-weighted forecasting, batter-pitcher matchups, bullpen usage, and plate-appearance Monte Carlo game simulation.
MLB-trained xSLG and swing-decision models feed the NCAA hitter app; NCAA TrackMan trains the Stuff+, whiff, and pitch-location models. The model index connects trained artifacts to their source notebooks. Game-level MLB research has its own methods, experiments, and evaluation reports below.
- Coaches use these tools every week. USD's staff uses the hitter and pitcher apps, the transfer-portal board and the per-game PDF reports (samples).
- One method at two levels. Stuff+ and whiff models are trained on NCAA TrackMan. The xSLG and swing-decision models are trained on MLB Statcast and applied to NCAA pitches in the hitter app (model index).
- A dbt/DuckDB warehouse runs from committed fixtures with no credentials. It rebuilds the transform layer as staged, tested models. The SQLite pipeline still runs the operational workflow (try it).
- Designed around a 2FA wall. The NCAA portal login needs a coach's 2FA code every time, so about 90 percent of the portal workflow runs from public sources with no login at all (design).
The hitter app moves from strike-zone heatmaps to spray charts and hitter metrics. The pitcher app compares pitch metrics, location and movement profiles. These paced screenshot sequences show three states from each running app, using the apps and data already in this public repository.
| Work | What it provides | Start here |
|---|---|---|
| Hitter app | xSLG, swing/take decision value, heatmaps, spray charts, and rolling trends | Hitter app |
| Pitcher app | Stuff+, whiff probability, movement, command and release plots, ideal locations, and reports | Pitcher app |
| Transfer portal | Multi-source ingestion, identity resolution, enrichment, fit scoring, alerts, and the coach board | Portal pipeline |
| Play-by-play reporting | Situational battle scoring and individual-game and season PDF reports | Pipeline and sample reports |
| Pitch-level research | Arm angle, shoulder coordinates, 3D wOBA, effective velocity, pitch mix, bunts, umpire analysis, and scouting notebooks | Research notebooks |
Pitch-location analysis from the scouting tools built for USD coaches.
Pitch movement colored by Stuff+, with pitcher and date filters.
sources ──▶ ingest/dedupe/resolve ──▶ SQLite + dbt warehouse ──▶ models ──▶ apps + board
D1Baseball staging / intermediate Stuff+ hitter app
VerbalCommits / marts, contracts xWhiff pitcher app
X/Twitter + source freshness xSLG Netlify board
CSV paste run value (Supabase RLS)
PerfectGame bio
Sidearm bio
TrackMan
The MLB pipelines build player and team estimates from public play-by-play and game logs, combine them into game forecasts, and evaluate them across seasons. The simulator also models how a game unfolds, including the starter's exit and which reliever is likely to enter.
Can a public-data model beat Vegas? No, and the test says how close it came. A model built only from Retrosheet and MLB StatsAPI was frozen, then scored once on 6,451 games from 2023 to 2025. Its log loss trails the no-vig closing line by 0.0036 per game (0.0018, borderline, once 12 dates with late-scraped "closing" lines are dropped). Its bets at the opening line moved toward the close by about 2 probability points, but the return cannot be told apart from zero. The rules for every verdict were written down before the test data was scored. Read the report.
| Work | What it provides | Methods and results |
|---|---|---|
| Run expectancy and recency | Rolling hitter, starter, bullpen, and team estimates; decay windows, offseason carry, shrinkage, and recent-form experiments | Recency study and R pipeline |
| Batter-pitcher matchup models | Handedness-specific outcome rates combined through log5, park adjustments, times through the order, bullpen availability, and walk-forward win forecasts | Model card and matchup experiments |
| Monte Carlo game simulator | Plate appearances, batting order, base-out transitions, starter hook, reliever selection, workload, win/run distributions, and hitter-pitcher exposure | Simulator design and implementation |
| Evaluation and market comparisons | Calibration, moneyline and totals benchmarks, closing-line value, conventional sabermetric baselines, and forward-season testing | Research report and evaluation figures |
| Reliability and model development | Sample-size stabilization, shrinkage tuning, context ablations, and comparisons of model variants | Research report and experiment log |
flowchart LR
A["Retrosheet and MLB StatsAPI"] --> B["Player and team history available before each game"]
B --> C["Decay and shrinkage estimates"]
C --> D["Recency-based forecasts"]
C --> E["Handedness and park-adjusted matchups"]
E --> F["Walk-forward win model"]
E --> G["Plate-appearance game simulator"]
D --> H["Season-based evaluation and research reports"]
F --> H
G --> H
Related projects: original R pipeline and WanBattedBall, a CatBoost entry in Nick Wan’s NWDS competition.
Build and test the staged DuckDB transform layer with the committed fixtures in
warehouse/sample_data/. Python 3.10+; no credentials required.
The SQLite pipeline still runs the operational workflow.
pip install -e ".[warehouse]"
cd warehouse
cp profiles.example.yml profiles.yml
dbt deps
dbt build --profiles-dir .See the warehouse guide for the model layout and migration plan.
| Area | Size | Notes |
|---|---|---|
src/ncaa/portal/ |
28 files, 5,296 lines | Portal ingest, dedupe, entity resolution, enrichment, geo, program tier, rollup, fit scoring, alerts |
src/ncaa/pbp/ |
26 code files, 6,550 lines | NCAA play-by-play acquisition (Playwright plus ncaa-api.henrygd.me), situational "battle" scoring, season PDF reports |
src/ncaa/cli.py |
336 lines | init, import-excel, ingest, enrich-643, evaluate, board, run-all |
warehouse/ |
18 SQL files, 925 lines | dbt-duckdb rebuild of the transform layer: 3 staging, 1 intermediate and 7 mart models (dims, fct_pitch, season marts), 3 singular tests, a pitch-type seed, model contracts, source freshness and sample fixtures |
apps/hitter/, apps/pitcher/ |
5,275 lines | The two Streamlit apps coaches open, with their 10 model artifacts |
scripts/ |
22 files | NCAA bio fetchers (D1/D2/D3/portal), school geocoding, Excel import and export, board HTML build, Netlify publish, Supabase RLS verify |
tests/ plus the pbp tests |
16 pytest files, 103 test functions | bio, board, d1baseball, dedupe, geo, overlay, paths, RLS schema, pipeline, program tier, resolve, rollup, speed, status, battle calc, and a guard that the battle converter stays linear in input size |
research/notebooks/ |
20 notebooks | 8 MLB, 12 NCAA, outputs stripped (verified 2026-09-16: none carry embedded outputs). Every shipped model was trained in one of these |
research/r/ |
MLB pipelines, studies, simulator, and testthat suite | Run expectancy, recency and matchup models, bullpen usage, Monte Carlo game simulation, market comparisons, and forward-season evaluation. The original pipeline is preserved in research/r/legacy/. |
docs/ |
10 design docs | Pipeline design, 2026 sourcing, capabilities and blockers, Netlify and Supabase recipes, the consolidation plan |
Model provenance, which notebook trained which artifact: models/README.md.
The two Streamlit apps and the coach board are in active use. The dbt warehouse is a staged
rebuild of the transform layer and has not cut over; the SQLite pipeline is still what runs.
CI (.github/workflows/ci.yml) runs ruff, pytest, dbt build on the fixtures and the testthat suite.
Everything under research/ is exactly that, and several notebooks depend on a TruMedia or
6-4-3 export this repo does not carry.
The pipeline runs from a fresh clone without credentials, but it needs portal entries to rank.
Those come from the coaching staff's transfer-portal workbook (sheets Names + Notes,
All Divisions Pitcher Data, Hitter Hot Board Data, RosterInfo), which is not committed, or
from the live sources. Without either, evaluate and board run and produce an empty board.
6-4-3 Charts stat exports are licensed and are not committed either; data/643_exports/README.md
gives the format to drop your own into.
pip install -e ".[scoring,ncaa-stats]"
python -m ncaa.cli init --force # create db/baseball.db from db/schema.sql
python -m ncaa.cli import-excel path/to/workbook.xlsx # seed portal entries
python -m ncaa.cli enrich-643 --dir data/643_exports # load your own 6-4-3 exports, if licensed
python -m ncaa.cli evaluate # compute fit scores
python -m ncaa.cli board --limit 150 # write data/hot_board.csvLive sources need a config.yaml (copy config.example.yaml). The D1Baseball adapter needs a
session cookie, and the Supabase overlay needs project keys; both degrade to no-op without them,
so the rest of the pipeline still runs.
The apps pin numpy against each other, so install one per environment:
pip install -e ".[hitter]" && streamlit run apps/hitter/streamlit_app.py
pip install -e ".[pitcher]" && streamlit run apps/pitcher/streamlit_app.pyFor the credential-free warehouse build, use the sample dbt quickstart above.
profiles.yml is gitignored; the example holds two local DuckDB paths.
The MLB R pipeline runs on its own, and its tests need no network:
Rscript research/r/tests/testthat.R
Rscript research/r/mlb/run_season.R 2019 # one season end to enddocs/samples/ holds real per-game battle reports the pbp pipeline generated for the 2026
season. The coach board itself (scripts/build_html.py) is generated locally and not committed,
because it names athletes and carries fit scores.
A manual Excel hot board plus a call-assignment spreadsheet. The interesting constraint was that
USD's sso.ncaa.org login needs a 2FA code from a coach on every use, so roughly 90 percent of
the workflow is built to need no login at all: entries are detected from public sources, stats
come from collegebaseball against stats.ncaa.org, and the login is spent only on contact
fields for the top call targets. docs/transfer-portal-pipeline.md has the full design.
USD's own TrackMan data is committed on purpose, because an app nobody can start is a
screenshot; USD branding, names and data are used with permission. Data licensed or scraped from
third parties (6-4-3 Charts exports, PerfectGame, Sidearm and D1Baseball bios) and the generated
recruiting boards are kept local and gitignored. The NCAA play-by-play under data/pbp/ comes
from public box scores.
Secrets are a separate matter and are not committed: config.yaml, secrets/, .env, keys and
service-account JSON are all ignored, and git ls-files is checked against that list before any
push.
More of my work and contact details are at twhite.dev.



