Skip to main content

Independent product

College football, beyond the scoreboard

CFB Model Lab

I built CFB Model Lab to explore why my predictions disagreed with betting lines. It brings Python modeling and college football research into a product I can use each week.

View original
Season metrics explorer with season, metric, and conference filters above an offense-versus-defense scatterplot

A season at a glance. Each point is a team; the controls change the comparison. This capture shows the partial 2026 season through Week 2.

Source: Original capture of CFB Model Lab · 17 September 2026

Contribution
Product design and AI-assisted development
Facts
My work
Product direction, interface design, Python modeling, and AI-assisted development
Built with
Python, pandas, scikit-learn, XGBoost, SQLite, Next.js, React, and TypeScript
Foundation
CollegeFootballData APIs, Starter Pack, Model Training Pack, and external rating sources

I wanted to understand the numbers

I've followed college football since I was a kid. When the BCS came along, I became interested in how the rankings worked. I wanted to understand the calculations behind the decisions, and that curiosity stayed with me.

I was especially interested in the Mid-American Conference, where teams receive less attention. I wanted to look beyond the record and understand how those teams actually played. Learning statistics such as expected points added, or EPA, gave me another way to look at a game.

That interest eventually led me to building models and comparing their predictions with the betting lines. I wanted to get better at handicapping games. When my numbers disagreed with the market, I wanted to understand why. CFB Model Lab became the place where I could work through those questions.

The first attempt went wrong in all kinds of ways

I started with the CollegeFootballData Starter Pack and Model Training Pack because the Python and modeling work were unfamiliar to me. The examples gave me somewhere to begin. I used AI coding tools to help turn them into an application while I learned how the pieces worked.

Over roughly a year and a half, I kept trying things, getting stuck, and starting again. The first attempt went wrong in all kinds of ways. More capable coding tools helped with the second attempt, but so did having a better understanding of what I was asking them to build.

The interface was the part I knew how to think through. I could use my day-to-day UX skills to decide what belonged together, what needed an explanation, and how to make the research useful when I came back to it the next week.

Start with the week's games

The landing page brings the week's games, my model's projections, and the market lines together. I can look through the slate, find a disagreement, and choose a game to investigate. That became the starting point for my weekly research.

I kept the selected matchup beside its supporting evidence so I could examine the prediction without losing the comparison. A large gap between the model and the market gives me a reason to look closer. The results still have to show whether there was an advantage.

Compare the call with the evidence

View original
CFB Model Lab matchup explorer with Buffalo selected, model and market margins, and historical evidence beside the projection

Live application captured on 17 September 2026. The selected matchup keeps the projection, comparison, sample size, and historical limitation together. Values are a dated snapshot, not a current recommendation.

Source: Original screenshot of the public CFB Model Lab application, cropped to the matchup explorer

I also keep the model's performance and my own betting record in separate views. The model still needs tuning, and the results haven't been great. I wanted a place to keep track of those results and go back over the calls that didn't work out.

Look more closely at the teams

I added the full 2026 schedule and individual team profiles so I could move from a game into the teams behind it. As I learned more, I added different rating systems to compare how they assessed the same programs.

Buffalo gives this flow a concrete starting point. From the schedule, I can open the Penn State game, then follow Buffalo into its profile. The game asks how the teams compare this week. The profile helps me step back and look at how Buffalo has played over time.

Follow a game into the team behind it

View original
Buffalo search and Week 3 filter showing the September 19 game at Penn State

Start with Buffalo in the 2026 schedule. Searching a team opens all weeks; selecting Week 3 narrows the result to Penn State. The date stays visible beside the provider's week number.

Source: Original live application capture · 17 September 2026

View original
Buffalo at Penn State matchup with linked team names and dated model and market comparisons

The schedule opens this matchup. The linked Buffalo name leads into the team profile, keeping research a continuation of the game comparison. These are saved model outputs, not a current recommendation or evidence of a profitable advantage.

Source: Original live application capture · 17 September 2026

View original
Buffalo profile with a historical per-play curve and a separate adjusted weekly ratings table

Buffalo's profile separates its raw historical curve from the opponent-adjusted weekly table. The 2026 curve is partial through provider Week 2. One adjusted weekly row is available here; it cannot establish a season-long trend.

Source: Original live application capture, scrolled to chart and table · 17 September 2026

Putting those views together helped me work through an important distinction: a team's history and this week's forecast answer different questions. I kept the raw curve separate from the ratings that account for opponent strength, with the partial-season label beside the chart. Otherwise, similar-looking numbers could invite a comparison they cannot support.

The season explorer gave me a way to see teams together on a chart. It puts offense and defense on separate axes, with filters for season and conference. I can look for a pattern across the field, then narrow the comparison to one conference.

The whole field, then your conference

View original
The same metrics explorer filtered to the SEC in 2025, showing sixteen team points

Same tool, a tighter question. Selecting 2025 and SEC redraws the chart and updates the team table. These are raw per-play statistics, before opponent adjustment.

Source: Original capture of CFB Model Lab · 17 September 2026

I connected the filters, chart, and sortable table so I could move from a visual impression to the actual values. Working on that connection was a chance to practice data visualization with a subject I already wanted to spend time exploring.

What if these two teams played?

I also wanted to compare teams that weren't scheduled to play each other. That became the hypothetical matchup tool. I can choose two teams, compare their strengths, and change the venue to see how the projection responds.

Choose the teams. Change the venue.

View original
Oregon and Ohio State in the hypothetical matchup tool with Team A Hosts selected and a projected margin of plus 5.8

Oregon vs. Ohio State using the published 2025 model. Switching from a neutral field to Oregon hosting changed the displayed margin from +3.0 to +5.8. The win estimate is a conversion of that margin, not an independent forecast.

Source: Original capture of CFB Model Lab · 17 September 2026

I brought the team comparison and prediction into the same view. Changing the venue makes the home-field assumption visible, and the team statistics give me something to examine alongside the result. These hypothetical games stay separate from the schedule and its market lines.

A place for the next question

The Lab grew as I tried different ways of researching the game. Backtests, rating comparisons, and detailed statistical views each gave me another way to examine what I was seeing. The collection reflects the questions I kept coming back to.

The fourth-down explorer is one example. It lets me look at how often teams go for it in different score situations and how that behavior has changed over time. Historical play data becomes something I can explore through charts and a searchable team table.

The game looks different when you change the question

View original
Fourth-down charts comparing go-for-it rates by score situation and season

Two views from the fourth-down explorer: score situation and change over time. The source spans 2003–2026; 2026 is partial. The charts describe what teams did, not the optimal decision.

Source: Original capture of CFB Model Lab · 17 September 2026

Read the situation, then compare the teams

Field position and distanceView complete original
Original Python analysis heatmap of fourth-down go rates by field position and yards to go, with sample counts

Detail of the original Python analysis export, retained in the 29 July 2026 source revision. Cells show sample counts beside the rates; the thinnest samples withhold a percentage. The script uses 2023–2025 regular-season plays. Open the complete original to see both charts.

Source: Original CFB Model Lab analysis export · source revision 6a8cca08

The heatmap asks how the situation changes a decision. The team chart asks who tends to go for it. Keeping counts beside the rates makes the thin samples visible. I carry that same concern into the application, where a percentage needs enough context to be useful.

Season previews gave me a different challenge. I wanted to take a season's worth of information and turn it into detailed research reports I could return to. That meant deciding what mattered and organizing it into something readable.

Turning research into something I could return to

The useful shift was from exploring one statistic to organizing a season's research around a team. A report needed to make the assumptions understandable as well as give me a conclusion to revisit. That is the part of synthesis I wanted to practice.

Scope

The public preview index reported "No season previews staged for this build" when checked on 17 September 2026. An actual report was unavailable to capture, so this account of the intent is not accompanied by a verified report example.

I wanted all of this in one place I could keep developing, without paying for another research service. The schedule, team research, experiments, and results became parts of the same personal workspace.

Being confident is something to test

Picking the winner and estimating a win probability are different tasks. If a model repeatedly says 75 percent, I want to know whether those teams win about three games in four. That question became the calibration view.

Compare the confidence with what happened

View original
Reliability chart comparing predicted win rate with actual win rate across five probability ranges

The published historical calibration view, captured on 17 September 2026. The diagonal marks agreement; the points compare grouped forecasts with outcomes. This is an application artifact, not an independently reproduced performance result.

Source: Original capture of CFB Model Lab · 17 September 2026

The chart is paired with a table of probability ranges, game counts, predicted rates, actual rates, and the gap between them. Expand a week to inspect its contribution. Keeping the sample size beside the percentage matters: a bucket with six games should not look as settled as one with more than a hundred.

Brier score and log loss add another way to question the model. Both evaluate the probabilities; log loss penalizes confident mistakes more strongly. The interface explains those measures alongside winner accuracy, without treating any of them as profit.

Different football questions need different models

I learned to separate a description of what a team has done from a prediction of what it might do next. The opponent-adjusted efficiency board describes completed play. A separate local SRS workflow estimates team strength from scoring margins and opponent strength, with prior-season carryover, venue adjustments, a cap on large margins, and separate overtime treatment.

The probability model combines several statistical approaches to estimate how likely a team is to win. Twelve statistical families each contribute offense and defense values for both teams. That makes 48 adjusted inputs; talent and Elo for each team bring the total to 52 continuous inputs. Week and conference are additional categorical fields.

Following the supplied Model Training Pack's recipe, logistic regression, XGBoost, and random forest feed a logistic combining model. A separate margin workflow predicts a point difference. The hypothetical matchup screen converts its margin to a win estimate, so I keep that conversion distinct from the trained probability model.

External ratings need the same care. The research importer keeps provider variants and capture times, matches team identities, and preserves each source's venue convention. Adding home advantage twice, or treating a season-end rating as a pregame observation, would produce a plausible-looking comparison with the wrong meaning.

Keep the question attached to the number

Label descriptive efficiency, point-scale strength, projected margin, and win probability separately. A new model should not quietly replace the meaning of a familiar ranking.

A model must not know how the game ended

The project's recorded audits found that post-game Elo had inflated an earlier backtest. That made the timing of a value as important as the value itself. A Week 6 projection must not use plays from Week 6 just because they are present in today's dataset.

The shared Python cutoff selects only earlier regular-season weeks. Postseason has its own rule, using the completed regular season rather than mistaking a bowl's week-one encoding for the season opener. Missing coverage returns a named reason so the caller can refuse the result or disclose an approximation.

Archived predictions preserve what was recorded before kickoff. Historical reruns remain a separate kind of evidence, especially when the source data has since been corrected or the seasons have already informed development. This distinction changed how I presented results and what I was willing to claim about them.

The research has to survive the next refresh

The documented refresh workflow imports completed games and plays, rebuilds the dependent statistics, preserves the outgoing forecast card, and prepares the next slate. Python writes the data that the Next.js application publishes as static JSON. Training and API credentials stay outside the browser.

Captures carry timestamps and hashes. Before a build, freshness checks compare the source receipts with the derived outputs. The published freshness record can expire in the browser even if no new deployment arrives, making a missed update visible.

A process lock prevents overlapping refreshes from rewriting the same generated files. Failed runs restore local generated state, while preserving raw captures and audit records. Deployment has its own check that compares the published JSON with the local build. A successful upload and a verified update are separate facts.

Matching names was not enough

In September 2026, I investigated whether CollegeFootballData's opponent-adjusted season metrics could replace inputs in my pregame model. All 134 teams in the captured season matched. Ten of the twelve statistical families had candidate fields. That looked promising, until I checked when those numbers were known.

The source supplied season-level metrics without a verified pregame cutoff. A full season's numbers cannot stand in for what was known before a particular game. The compatibility report therefore kept zero valid comparison pairs and left the existing model unchanged.

Preserve the unanswered question

Keep the new source as a season-level research candidate. Do not substitute it into pregame predictions until its timing and included games can be verified.

Scope

Based on the project's 15 September 2026 compatibility report and recorded source receipts. This review did not rerun model training or establish a prediction improvement.

Make a source problem reproducible

The implementation has two ways to request the same data: a live connection and a replay of saved provider responses. The replay exercises the field mapping without a network connection or API key. It checks both the requested endpoint and its parameters, so a response from one week cannot silently stand in for another.

This is the engineering work I want beside the interface. A chart can be legible and still answer the wrong question if its inputs mean something different from its labels.

What I learned by building it

I directed the product and developed it with coding agents. My work includes the interface, public API integrations, Python rating and modeling workflows, and implementation review. Building the application gave me a reason to learn what happens to the data before it reaches the screen.

Python prepares the data, and SQLite and static JSON carry it into the Next.js and React application. Behind the charts are opponent-adjusted statistics, SRS and Elo ratings, and separate models for margins and probabilities. Learning those distinctions helped me decide how to describe the numbers in the interface.

The CollegeFootballData packs remain the foundation, and the opponent-adjustment work uses Bud Davis's published ridge-regression method. I extended those starting points into the research workflows and application shown here.

A missing statistic could change whether I could make a comparison. A model's limitations could change the words I used around a result. Working through those details brought the data work and my design practice together.

The model still needs work. I now have a place to investigate football questions, keep track of the answers, and try something else when an approach doesn't hold up. Building it has let me follow my design decisions further into the software that makes them work.

Results

  • A personal research application connecting weekly games, team comparisons, visual exploration, and model evaluation.
  • Hands-on work across product design, Python modeling, data integration, and AI-assisted implementation.

Evidence limit

Screens and interactions reviewed on 17 September 2026. The data pipeline was reviewed separately in source, not rerun for this case study. The captures show dated model outputs, not current recommendations or evidence of improved prediction accuracy.

Resume and contact