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:

  1. A Chaos Index β€” a 0–100 score for how likely each game is to go sideways
  2. A Key Edge β€” one specific, non-obvious reason the outcome might surprise you
  3. Upset picks β€” where the AI actually picks the lower seed
  4. 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

View the full AI bracket analysis on prxps

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:

TypeWhat It Means
πŸ”₯ upset_alertAI picks the lower seed. High conviction.
πŸͺ™ coin_flipTrue 50/50. No edge either way.
πŸ’€ trap_gameFavorite has a specific vulnerability. Watch out.
⚑ pace_mismatchOne team needs pace, the other controls it.
πŸ₯Š style_clashDefensive/offensive system exploits exist.
🧊 dominantExpected result. Ice cold.
πŸš€ momentum_playA team is hot right now. First Four energy.
πŸ†• first_timerTournament 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.

Tags

aisportsprxpsgemma