prxps: I Let AI Analyze Every First-Round March Madness Matchup
It's 5 PM on bracket submission day. I have 12 hours.
My bracket is still blank. And I have an AI model sitting idle.
So I did what any reasonable person building a sports predictions app would do: I fed the entire 2026 NCAA Tournament first-round bracket into my AI pipeline and asked it to do what ESPN analysts do β but without the narrative bias.
The Setup
prxps has had a Gemma-3-27b integration running for a few months. The original idea was behavioral embeddings β game recommendations. Think Spotify Discover Weekly, but for sports bets.
But for March Madness, I wanted something different. I wanted:
- A Chaos Index β a 0β100 score for how likely each game is to go sideways
- A Key Edge β one specific, non-obvious reason the outcome might surprise you
- Upset picks β where the AI actually picks the lower seed
- An Insight Type β categorical labels like
coin_flip,trap_game,pace_mismatch,upset_alert
Not "Duke is really good." That's useless. I wanted why a specific game has a structural mismatch that could change the outcome.
What the AI Found
The Most Dangerous Upsets
The model flagged Saint Louis over Georgia as its most confident upset call. At 28-5, Saint Louis is legitimately underseeded β a 9-seed with a record that screams 6 or 7. The selection committee undervalued the Atlantic 10 this year and the Billikens are paying for it in seeding. Georgia at 22-10 in the SEC is good, but not dominant.
Santa Clara over Kentucky was the second upset pick. This one is wilder β Santa Clara beat Gonzaga this season. A program that beats a 30-win Gonzaga squad has already proven it can compete at this level. Kentucky at 21-13 is having a down year by their standards. The Wildcats' pedigree is real, but pedigree doesn't score points.
UCF over UCLA rounds out the top upset picks. UCF joined the Big 12 and survived the season. That's preparation. UCLA's schedule had soft spots. The 10-seed edge in the 7/10 matchup is real β those games go to the underdog ~40% of the time.
The Chaos Index Results
The model scores each game 0β100 for "chaos potential" β a composite of seed differential, conference strength gap, recent form, and system matchup.
The highest chaos games:
- Utah State vs Villanova (58) β Utah State at 28-6 is massively underseeded as a 9
- TCU vs Ohio State (55) β True coin flip, neither team is significantly better
- Saint Louis vs Georgia (54) β A-10 vs SEC, record says upset
- Iowa vs Clemson (53) β Big Ten experience edge
The lowest:
- Arizona vs LIU (4) β #2 overall seed vs NEC
- Duke vs Siena (4) β #1 overall vs MAAC
The Insight Type System
I created eight categories to classify each matchup:
| Type | What It Means |
|---|---|
π₯ upset_alert | AI picks the lower seed. High conviction. |
πͺ coin_flip | True 50/50. No edge either way. |
π trap_game | Favorite has a specific vulnerability. Watch out. |
β‘ pace_mismatch | One team needs pace, the other controls it. |
π₯ style_clash | Defensive/offensive system exploits exist. |
π§ dominant | Expected result. Ice cold. |
π momentum_play | A team is hot right now. First Four energy. |
π first_timer | Tournament debut pressure factor. |
The pace_mismatch category caught some interesting games. Saint Mary's vs Texas A&M is a classic: the Gaels will slow this to 58 possessions. A&M needs pace to win. If Saint Mary's controls tempo, the SEC athleticism advantage gets neutralized.
What I Built
Everything lives at tourney picks with AI on prxps.
The core is a typed data structure for each matchup β InsightType as a union, Matchup as an interface:
type InsightType =
| 'upset_alert'
| 'coin_flip'
| 'trap_game'
| 'pace_mismatch'
| 'style_clash'
| 'dominant'
| 'momentum_play'
| 'first_timer';
interface Matchup {
id: string;
region: 'East' | 'West' | 'South' | 'Midwest';
seed1: number;
team1: string;
record1: string;
seed2: number;
team2: string;
record2: string;
chaosIndex: number; // 0β100
insightType: InsightType;
keyEdge: string; // the non-obvious structural edge
aiPick: string;
confidence: 'Low' | 'Medium' | 'High';
deepDive: string; // Gemma's full analysis
}
The Chaos Index is a single number per game β hand-tuned weights across seed differential, conference strength gap, system matchup, and momentum signals. For example, Miami (OH) vs Tennessee:
{
id: 'm-6-11',
region: 'Midwest',
seed1: 6, team1: 'Tennessee', record1: '22β11',
seed2: 11, team2: 'Miami (OH)', record2: '31β1',
chaosIndex: 62, // highest in the field
insightType: 'upset_alert',
insightLabel: 'π₯ Cinderella Alert',
keyEdge: `Miami (OH) survived the First Four at 31-1 β
the best record in the entire field. They are the hottest
team in the bracket. Tennessee at 22-11 is a vulnerable
6-seed. This is the most dangerous upset game left.`,
aiPick: 'Miami (OH)',
confidence: 'Low',
}
Utility functions keep the SvelteKit components clean:
// Filter by region for the tab UI
export function getMatchupsByRegion(region: 'East' | 'West' | 'South' | 'Midwest') {
return BRACKET_2026.filter(m => m.region === region);
}
// Pull every game where AI picks the lower seed
export function getUpsetPicks() {
return BRACKET_2026.filter(m => m.aiPick === m.team2);
}
// Average chaos score across the full field
export function getAverageChaos() {
return Math.round(
BRACKET_2026.reduce((sum, m) => sum + m.chaosIndex, 0) / BRACKET_2026.length
);
}
The whole thing β data, analysis, page β took about 2 hours. The AI did the research; I built the frame.
What I Actually Think
A few things struck me building this:
The model doesn't have recency bias. When I think about Kansas, I think about their history, their brand, their Final Four appearances. The model just sees 23-10 and asks: is this record convincing? At the 4-seed line, the answer is usually. It doesn't carry the weight of the brand.
Conference quality signals matter more than record. The model consistently flagged teams with strong records in weak conferences as risk factors β not because they're bad, but because the gap in competition level creates unknown variables. Cal Baptist at 25-8 in the WAC is a different data point than Kansas at 23-10 in the Big 12.
The chaos index reveals structural upsets before they happen. VCU's HAVOC defense gets a 46 on the chaos index against North Carolina. That's the model identifying that 27-7 VCU with elite turnover-forcing stats is a structural threat to a UNC team that's been inconsistent. Not just "the upset feels right" vibes β an actual defensive stat profile that suggests vulnerability.
What's Next
This is a v1 experiment. The Chaos Index weights are educated guesses right now. What I want to build:
- Historical backtesting β how does Chaos Index correlate with actual upset frequency?
- Live updates β as games are played, update the model's "read" on remaining matchups
- Bracket scoring integration β connect to prxps picks so users can see how their choices compare to AI picks
The sports predictions space is full of "predictions" that are just syndicated lines with a different UI. The interesting work is finding the structural edges β the pace mismatches, the style clashes, the first-timer pressure factors β that don't show up in a simple spread.
That's what I'm trying to build at prxps.
Tourney picks with AI β live on prxps. Built with SvelteKit + Gemma-3-27b. Chaos is a feature, not a bug.