Prompt Chaining: How I Used ChatGPT to Build a 3-Step NFL Pick Pipeline (and Why It Works for Business Too)
I built a three-step ChatGPT workflow to pick winners for NFL Wild Card weekend (Jan 10–12, 2026). Not because I’m a professional sports bettor. Because I’m in a football pool. And I want to win.
Here’s the pool structure: every week you pick the winner of each NFL game. Whoever picks the most winners wins the week. Simply being in the top half nets you a staggering $1. End up in the bottom half and it costs you $1. High stakes.
Yes, I spent tens of hours on this originally. Yes, I was the top winner one week. Yes, I spent more hours on this than everyone else in the pool combined. No, I did not win the pool. The ROI was offensive. The lessons were priceless.
As a side note, if you think you’re going to beat Vegas using ChatGPT, think again. Vegas has access to way more data than you do, and better models than you do. When I did this exercise, ChatGPT often landed on the same picks Vegas would. That’s not a failure, it’s what you should expect when you and Vegas are both looking at widely available stats.
So what’s the point?
This project fundamentally changed how I use ChatGPT (and LLMs in general) because it forced me to learn two concepts that now show up in my day-to-day work: ChatGPT prompt engineering and prompt chaining.
This post is a practical example of how to build a simple multi-step prompt chain by using ChatGPT to help you write the prompts themselves.
The Big Shift: ChatGPT is probabilistic, your workflow should not be
ChatGPT is probabilistic, not deterministic. Translation: even with the same prompt, you can get different phrasing, different structure, and occasionally different behavior in response. It’s not a function that guarantees the exact same output format every time.
That’s fine for casual Q&A. It’s a problem when you’re building something repeatable that scales.
So instead of hoping the model is consistent, you design a workflow that forces consistency:
- Strict input and output contracts (usually a schema).
- Validation up front.
- Separate steps for separate jobs.
- Explicit failure behavior.
- Clear tool rules (browsing allowed vs banned).
That’s prompt engineering. And when those steps hand structured outputs to each other, that’s prompt chaining.
Why prompt chaining is useful (and why most people skip it)
Most people start with a single mega-prompt that tries to do everything:
“Read my file, research everything, analyze it, pick winners, give confidence, format it nicely.”
That sounds efficient, but it’s usually brittle.
Prompt chaining wins because it separates concerns:
- Validate and standardize the input (garbage in gets caught early)
- Enrich with facts (gather data, store sources, handle missing values)
- Decide using only the enriched dataset (no sneaky external inputs)
This is the same pattern you use in any real analytics workflow: clean data first, enrich second, decide or report third.
When something is off, you can debug the right stage instead of staring at one giant prompt wondering which part went rogue.
Why you should use ChatGPT to help you write prompts
This is the part that most people miss.
They open ChatGPT and try to “write the perfect prompt” solo, like it’s a one-shot request they need to nail on the first try. That’s not how you get reliable outputs.
The better approach is to use ChatGPT as a prompt co-author. Your job is to provide the goal, constraints, and context. ChatGPT’s job is to surface edge cases and turn your request into an executable spec.
The key move is simple: ask ChatGPT to ask you clarifying questions before it writes the prompt.
That single tactic is the difference between: “Sure, here’s something that kind of works” and “Here’s a prompt that behaves like a reliable pipeline step”.
The meta-prompts that built the prompts (this is the part you should copy)
Below is the “behind the scenes” layer. This is what I actually typed to ChatGPT to get the prompts to a working state.
Meta-prompt #1: The kickoff request (Prompt chain setup)
What I asked ChatGPT (to start Prompt 1):
I am participating in a football pool. Every week, there is a list of NFL games that I simply have to pick the winners of. Whoever picks the most winners, wins the week. I want your help with a series of three prompts with the ultimate output being a prediction of who you think will win each NFL matchup for the current week.
Prompt 1 will focus on reading and validating an Excel file that has the current week’s matchups in them.
Prompt 2 will focus on doing research for each team and matchup.
Prompt 3 will use the research done in the second prompt to predict and pick a winner for each matchup.
With that context, let’s start with just the first prompt.
Prompt 1 will be only for reading and validating an Excel file, called “Football Picks.xlsx”, and outputting a json file of those matchups that will be used as an input to the second prompt.
There is only one tab called “Matchups” that you will read from. On that tab there are only three columns:
Date – the date of the game
Away Team – the visiting team
Home Team – the hosting team
I need you to write a prompt that will perform the specified operations. If there is a failure in validating or populating the json output, please stop and read out the error message.
Please ask me any clarifying questions before creating this first prompt.
This is the first takeaway: I wasn’t asking for picks. I was asking ChatGPT to help me build a workflow.
Meta-prompt #2: Clarifying questions (and why they matter)
ChatGPT came back with the exact kinds of questions that prevent later failure. Here are the ones that mattered most, with my answers.
Date format: Is Date a true Excel date or text?
Answer: True Excel date value.
Team naming: Full names vs abbreviations vs nicknames?
Answer: Common team nicknames only.
Row hygiene: Stop at first blank row or scan entire sheet?
Answer: Stop at the first blank line.
Duplicates: Fail or dedupe?
Answer: Deduplicate automatically.
Output shape: Array only or metadata + matchups array?
Answer: Metadata + matchups array, and reserve a research placeholder for later.
This is prompt engineering in plain language: define defaults, define edge cases, define the contract.
Meta-prompt #3: Refinements (how prompts become usable)
After Prompt 1 was basically correct, I made a refinement that turned it into a real pipeline step:
Refinement: make Prompt 1 output a downloadable file
I need to make one change. I would like it to output a downloadable json file. Keep everything else the same.
Later, when Prompt 2 was working but annoying to use, I made a refinement that improved the handoff and trust:
Refinement: make Prompt 2 easier to use and trust
This prompt did not output a clickable link to the matchups.json file. Please update the prompt to ensure this happens. In addition, add a short summary of how complete the stats are for each team.
Those refinements sound small, but they’re not. They are the difference between “did it work” and “the output is ready for the next step in the chain.”
Step 1: Validate → JSON (Excel ingestion that doesn’t hallucinate)
Step 1 exists for one reason: if the model doesn’t ingest your input correctly, everything downstream becomes confidently wrong.
Validation up front does three critical things:
- Proves the input was read correctly
- Standardizes the data into a predictable format
- Reduces hallucinations by removing ambiguity and forcing errors when needed
In my case, the input was an Excel file (Football Picks.xlsx) with a single tab (Matchups) and three columns: Date, Away Team, Home Team.
The Step 1 prompt enforces rules like:
- Stop at first blank row
- Normalize team nicknames to standard codes
- Deduplicate matchups
- Fail closed with explicit error messages
- Output a clean matchups.json file with a placeholder research object for Step 2
Prompt 1 (Validate → JSON Contract)
You are a data validator. Your job is to read an uploaded Excel file named “Football Picks.xlsx”, validate the “Matchups” sheet, and create a downloadable JSON file containing the validated matchups.
HIGH-LEVEL GOAL
– Read the Matchups tab, which contains exactly 3 columns: Date, Away Team, Home Team.
– Stop reading at the first completely blank row (all three cells empty).
– Validate and normalize each matchup.
– Deduplicate duplicate matchups automatically.
– Produce a JSON object (metadata + matchups array) that will be used by later prompts. Later prompts will add a “research” object under each matchup, so include an empty placeholder now.
– Write the JSON object to a file named: matchups.json, and provide it as a downloadable file.
FILE REQUIREMENTS
– The user will upload “Football Picks.xlsx”.
– The workbook must contain a sheet named exactly: Matchups
– The Matchups sheet must contain exactly these 3 columns (case-insensitive, extra spaces allowed):
1) Date
2) Away Team
3) Home Team
– No other columns should be present. If extra columns exist, error.
INPUT ASSUMPTIONS (USER CONFIRMED)
– Date is a true Excel date value (not text).
– Away Team and Home Team are common team nicknames (e.g., “Chiefs”, “Packers”, “49ers”), not full names or abbreviations.
PROCESSING RULES
1) Open the uploaded file “Football Picks.xlsx”. If it is not available, output:
ERROR: missing_file – Please upload “Football Picks.xlsx”.
2) Read the “Matchups” sheet.
3) Identify the header row and map the three required columns, case-insensitive: Date, Away Team, Home Team.
4) Starting from the first data row below the header:
– Read rows in order.
– If Date, Away Team, and Home Team are ALL blank in a row, STOP processing (do not read further).
– Otherwise, validate the row and add it to an in-memory list.
5) Deduplicate automatically:
– Define a deterministic game_id per row after normalization: “NFL-{date}-{away_team}-at-{home_team}”
– If the same game_id appears multiple times, keep the FIRST occurrence and ignore the rest.
6) Sort final matchups by date ascending, then away_team, then home_team.
VALIDATION RULES (FAIL-CLOSED)
– If any validation fails, STOP immediately and output a single plain-text error message (NOT JSON) that starts with “ERROR:” and includes:
– row_index (1-based, counting the first data row as row_index=1, excluding the header row)
– column name
– offending value
– what is required instead
Row-level validations:
A) Date
– Must be present.
– Must be a valid Excel date cell.
– Normalize to “YYYY-MM-DD” (no time component).
B) Away Team / Home Team
– Must be present, non-empty after trimming.
– Must be a recognizable NFL team nickname that maps to exactly one of the 32 team codes below.
– Away Team and Home Team cannot map to the same team code.
TEAM NORMALIZATION (NICKNAMES ONLY)
– Normalize input by: trimming whitespace, converting to uppercase, removing periods, collapsing multiple spaces.
– Map the normalized nickname to a 2–3 letter team code using this nickname dictionary:
ARI: CARDINALS
ATL: FALCONS
BAL: RAVENS
BUF: BILLS
CAR: PANTHERS
CHI: BEARS
CIN: BENGALS
CLE: BROWNS
DAL: COWBOYS
DEN: BRONCOS
DET: LIONS
GB: PACKERS
HOU: TEXANS
IND: COLTS
JAX: JAGUARS, JAGS
KC: CHIEFS
LV: RAIDERS
LAC: CHARGERS
LAR: RAMS
MIA: DOLPHINS
MIN: VIKINGS
NE: PATRIOTS
NO: SAINTS
NYG: GIANTS
NYJ: JETS
PHI: EAGLES
PIT: STEELERS
SF: 49ERS, FORTY NINERS, NINERS
SEA: SEAHAWKS
TB: BUCCANEERS, BUCS
TEN: TITANS
WAS: COMMANDERS
– If a nickname does not match any entry, error type unknown_team.
– If a nickname could match more than one team (unlikely with nicknames, but possible), error type ambiguous_team.
OUTPUT (SUCCESS ONLY)
If validation passes, create a file named “matchups.json” whose contents are ONLY the following JSON object (no extra keys):
{
“status”: “ok”,
“source_file”: “Football Picks.xlsx”,
“sheet”: “Matchups”,
“matchups”: [
{
“game_id”: “NFL-YYYY-MM-DD-AWAY-at-HOME”,
“date”: “YYYY-MM-DD”,
“away_team”: “AWAY_CODE”,
“home_team”: “HOME_CODE”,
“research”: {}
}
]
}
OUTPUT CONSTRAINTS
– On success: do NOT print the JSON in the chat.
– On success: provide the downloadable file “matchups.json” as the output artifact.
– After creating/attaching the file, output exactly this single line (and nothing else):
OK: matchups.json
Begin now by attempting to read the uploaded file.
Step 2: Research → Enrich JSON (facts only, no odds)
Step 2 is the data-gathering step. It should feel boring. That’s the point.
It reads matchups.json, looks up specific stats for each team, and writes the same file back out with the research section populated.
A key decision I made here: I hand-selected stats that are publicly available and reliably scrapable.
If you pick stats that are paywalled, hidden behind interactive widgets, or inconsistently published, your chain will break. You’ll spend your time fighting data access instead of building a repeatable AI workflow. (More on this in the Lessons learned section.)
For this chain, I used a short list that tends to be broadly available:
- Offensive EPA per play
- Point differential
- Offensive third-down conversion %
- Offensive red zone TD %
- Offensive yards per play
- Starting QB Total QBR
Also, I explicitly banned odds, spreads, and moneylines. Not because I’m above it. Because I didn’t want betting-market info leaking into the decision step and pretending it was analysis.
Then I added two “make this usable in real life” outputs:
- A clickable link to the updated matchups.json
- A completeness summary so you can quickly see what’s missing before you run Step 3
Prompt 2 (Research → Enrich JSON, No Odds)
You are a sports research assistant. EXECUTE this prompt (do not rewrite it, do not summarize it).
Your job:
1) Read an uploaded JSON file named “matchups.json”.
2) For EACH matchup, use Web search/browsing to collect specific stats for BOTH teams (2025 NFL regular season ONLY).
3) Populate the “research” field for each matchup.
4) Write a downloadable file named exactly “matchups.json”.
5) Output (A) a clickable link to the updated file and (B) a short completeness summary.
CRITICAL CONSTRAINTS
– You MUST use Web search / browsing for data gathering.
– You MUST NOT use, reference, or consider any Vegas odds, spreads, moneylines, implied probabilities, betting markets, or pick’em lines.
– Only the stats listed below are allowed inputs.
– Do NOT guess numbers. If you can’t find a stat after trying multiple sources, set it to null and log a warning.
– Use 2025 REGULAR SEASON only (not playoffs). If a matchup date is in January 2026, that is still OK; you still must use 2025 regular season stats.
INPUT FILE
– The user will upload “matchups.json”.
– If missing or invalid JSON, output:
ERROR: invalid_input – Please upload a valid “matchups.json” file.
EXPECTED INPUT SHAPE
{
“status”: “ok”,
“source_file”: “…”,
“sheet”: “…”,
“matchups”: [
{
“game_id”: “string”,
“date”: “YYYY-MM-DD”,
“away_team”: “TEAM_CODE”,
“home_team”: “TEAM_CODE”,
“research”: {}
}
]
}
STATS TO COLLECT (FOR EACH TEAM; 2025 REGULAR SEASON)
1) EPA (Expected Points Added) per Play — OFFENSE ONLY
2) Point Differential — season total
3) Third-Down Conversion Percentage — OFFENSE ONLY
4) Red Zone Efficiency — OFFENSIVE red zone TD%
5) Yards per Play — OFFENSE ONLY
6) Total QBR — projected starting QB’s ESPN Total QBR (season)
NO RANKS
– Do NOT include ranks anywhere.
FAILURE BEHAVIOR
– For each individual stat value, try up to 5 different sources (distinct sites/pages).
– If still unavailable after 5 tries:
– set value=null
– set source_url=null
– add a warning that includes the stat name and the attempted URLs.
– Never invent numbers.
SOURCE RULES
– Prefer reputable, consistent sources. Use the same source for a given stat across all teams whenever practical.
– Every non-null stat value MUST include a source_url.
– If sources disagree, prefer the most authoritative primary provider and note the discrepancy in warnings (do not average).
DERIVATION RULES (LIMITED)
– You may derive POINT DIFFERENTIAL only if you can reliably obtain Points For (PF) and Points Against (PA) for the 2025 regular season from a reputable source.
– point_differential = PF – PA (exact, not per-game).
– If derived, add a warning:
“derived_stat: point_differential from PF-PA; source:
”
– Do not derive other stats.
DATA NORMALIZATION RULES
– Keep team codes as-is (e.g., GB, CHI, SF).
– Percentages stored as numbers from 0 to 100 (e.g., 41.7).
– EPA/play stored as a decimal (can be negative).
– Point differential stored as an integer if possible.
– period must be exactly: “2025 regular season”
– generated_at must be today’s date in “YYYY-MM-DD”.
OUTPUT REQUIREMENTS
– Update the existing JSON by populating “research” for each matchup.
– Do NOT modify game_id, date, away_team, home_team.
– Write the updated JSON to a file named EXACTLY: “matchups.json”
– On success: do NOT print the JSON in the chat.
SUCCESS OUTPUT FORMAT (MUST FOLLOW EXACTLY)
After attaching the updated file, output:
Line 1 (must be a clickable markdown link):
OK: [matchups.json]()
Then a short completeness summary (no more than 20 lines total), one line per TEAM_CODE that appears in the matchups:
COMPLETENESS:
– /6 present; missing: [comma-separated stat keys]
Completeness counting rules:
– The 6 items are:
– epa_per_play_offense
– point_differential
– third_down_conversion_pct_offense
– red_zone_td_pct_offense
– yards_per_play_offense
– starting_qb.total_qbr
– An item counts as “present” only if value is NOT null.
– List missing items using these exact keys:
– epa_per_play_offense
– point_differential
– third_down_conversion_pct_offense
– red_zone_td_pct_offense
– yards_per_play_offense
– total_qbr
ERROR OUTPUT
– If any error occurs before the file is attached, output ONLY the error line (no other text).
RESEARCH OBJECT SCHEMA (MUST MATCH EXACTLY)
For each matchup, set:
“research”: {
“period”: “2025 regular season”,
“generated_at”: “YYYY-MM-DD”,
“no_odds_used”: true,
“teams”: {
“away”: {
“team_code”: “TEAM_CODE”,
“team_name”: “string”,
“starting_qb”: {
“name”: “string|null”,
“total_qbr”: { “value”: number|null, “source_url”: “string|null” }
},
“stats”: {
“epa_per_play_offense”: { “value”: number|null, “source_url”: “string|null” },
“point_differential”: { “value”: number|null, “source_url”: “string|null” },
“third_down_conversion_pct_offense”: { “value”: number|null, “source_url”: “string|null” },
“red_zone_td_pct_offense”: { “value”: number|null, “source_url”: “string|null” },
“yards_per_play_offense”: { “value”: number|null, “source_url”: “string|null” }
},
“warnings”: [“string”]
},
“home”: {
“team_code”: “TEAM_CODE”,
“team_name”: “string”,
“starting_qb”: {
“name”: “string|null”,
“total_qbr”: { “value”: number|null, “source_url”: “string|null” }
},
“stats”: {
“epa_per_play_offense”: { “value”: number|null, “source_url”: “string|null” },
“point_differential”: { “value”: number|null, “source_url”: “string|null” },
“third_down_conversion_pct_offense”: { “value”: number|null, “source_url”: “string|null” },
“red_zone_td_pct_offense”: { “value”: number|null, “source_url”: “string|null” },
“yards_per_play_offense”: { “value”: number|null, “source_url”: “string|null” }
},
“warnings”: [“string”]
}
}
}
TEAM NAME RESOLUTION (TEAM_CODE -> TEAM_NAME)
Use this mapping (exact strings):
ARI Arizona Cardinals
ATL Atlanta Falcons
BAL Baltimore Ravens
BUF Buffalo Bills
CAR Carolina Panthers
CHI Chicago Bears
CIN Cincinnati Bengals
CLE Cleveland Browns
DAL Dallas Cowboys
DEN Denver Broncos
DET Detroit Lions
GB Green Bay Packers
HOU Houston Texans
IND Indianapolis Colts
JAX Jacksonville Jaguars
KC Kansas City Chiefs
LV Las Vegas Raiders
LAC Los Angeles Chargers
LAR Los Angeles Rams
MIA Miami Dolphins
MIN Minnesota Vikings
NE New England Patriots
NO New Orleans Saints
NYG New York Giants
NYJ New York Jets
PHI Philadelphia Eagles
PIT Pittsburgh Steelers
SF San Francisco 49ers
SEA Seattle Seahawks
TB Tampa Bay Buccaneers
TEN Tennessee Titans
WAS Washington Commanders
EXECUTION PLAN (DO THIS)
1) Parse matchups.json.
2) For each matchup, resolve away_team/home_team team_name using the mapping above.
3) Determine projected starting QB for each team for the matchup date using Web search:
– Prefer a reputable depth chart source and/or an injury report source.
– Store QB name; if uncertain, set null and add a warning including the URLs you checked.
4) Retrieve the QB’s ESPN Total QBR for the 2025 regular season (season value, not a single game).
5) For each team, collect the five team stats via Web search:
– epa_per_play_offense
– point_differential (or derive from PF-PA per rules)
– third_down_conversion_pct_offense
– red_zone_td_pct_offense
– yards_per_play_offense
For each stat:
– Try up to 5 different sources.
– Record value + source_url if found.
– If not found, set value/source_url to null and add warning:
“missing_stat: ; tried: [url1, url2, url3, url4, url5]”
6) Populate research exactly per schema for each matchup.
7) Write and attach the updated matchups.json.
8) Output the clickable link line and the COMPLETENESS summary exactly per the required success format.
Begin now by reading the uploaded matchups.json.
Step 3: Predict → Table Output (no browsing, only JSON stats)
This step is where the picks happen, and it has one strict rule:
No web search. No browsing. Only the stats already in the JSON.
This matters because it prevents hidden inputs. If you allow browsing in the pick step, you’ll never be sure what the model used. You lose repeatability and you lose trust.
In this step, I wrapped the probabilistic model in a deterministic decision process:
- fixed weights for each stat category
- explicit tie-break rules
- a confidence score based on how strong the advantage is and how complete the inputs are
- readable output as a clean table
Prompt 3 (Predict → Table Output, No Browsing)
You are a prediction engine. EXECUTE this prompt (do not rewrite it, do not summarize it).
Your job:
1) Read an uploaded JSON file named “matchups.json”.
2) For EACH matchup, use ONLY the stats already present in the file to pick/predict the winner.
3) DO NOT use Web search/browsing or any external data in this step.
4) Print the picks to the screen as a clean markdown table (no file output).
ABSOLUTE CONSTRAINTS (FAIL-CLOSED)
– NO WEB SEARCH / BROWSING. Do not open links. Do not look up injuries. Do not fetch anything.
– If you used (or are about to use) browsing/search tools, STOP and output:
ERROR: browsing_not_allowed – This step must use only the provided matchups.json stats.
– Do NOT use Vegas odds, spreads, moneylines, implied probabilities, or any betting market info.
– Use ONLY the numeric values contained in matchups.json.
INPUT FILE
– The user will upload “matchups.json”.
– If missing or invalid JSON, output:
ERROR: invalid_input – Please upload a valid “matchups.json” file.
EXPECTED INPUT SHAPE (MINIMUM)
{
“matchups”: [
{
“game_id”: “string”,
“date”: “YYYY-MM-DD”,
“away_team”: “TEAM_CODE”,
“home_team”: “TEAM_CODE”,
“research”: {
“teams”: {
“away”: {
“team_code”: “TEAM_CODE”,
“team_name”: “string”,
“starting_qb”: { “name”: “string|null”, “total_qbr”: { “value”: number|null } },
“stats”: {
“epa_per_play_offense”: { “value”: number|null },
“point_differential”: { “value”: number|null },
“third_down_conversion_pct_offense”: { “value”: number|null },
“red_zone_td_pct_offense”: { “value”: number|null },
“yards_per_play_offense”: { “value”: number|null }
}
},
“home”: { … same keys … }
}
}
}
]
}
ERROR HANDLING
– If the input JSON does not have matchups[] with research.teams.away/home in the expected shape, output:
ERROR: invalid_schema – matchups.json is missing required research fields for prediction.
SCORING METHOD (DETERMINISTIC): “weighted-category-wins”
You will compare the away team vs home team on 6 inputs. Each input has a weight.
Inputs and weights:
– epa_per_play_offense (weight 2)
– point_differential (weight 2)
– total_qbr (starting_qb.total_qbr.value) (weight 2)
– third_down_conversion_pct_offense (weight 1)
– red_zone_td_pct_offense (weight 1)
– yards_per_play_offense (weight 1)
For each input:
– If either team’s value is null, do NOT award points for that category.
– Track the missing key in missing_inputs (use these exact keys):
epa_per_play_offense, point_differential, total_qbr, third_down_conversion_pct_offense, red_zone_td_pct_offense, yards_per_play_offense
– If both values are present:
– Higher value wins the full weight.
– If equal, split the weight evenly (e.g., weight 2 -> 1 point each).
– Total points across available categories produce scores.away and scores.home.
– max_points_available is the sum of weights for categories where BOTH teams had non-null values.
WINNER SELECTION
– Winner is the team with the higher score.
– If scores tie:
1) Pick the team with higher epa_per_play_offense (if both present)
2) Else higher point_differential (if both present)
3) Else higher total_qbr (if both present)
4) Else pick the HOME team (deterministic fallback) and set confidence to 1.
CONFIDENCE (1–10)
– Let M = max_points_available.
– Let D = abs(scores.away – scores.home).
– If M == 0: confidence = 1 and justification must say data missing.
– Else:
– confidence = 1 + round(9 * (D / M))
– clamp to [1, 10]
– If missing_inputs is non-empty, subtract 1 from confidence (but not below 1).
JUSTIFICATION (SHORT, STATS-BASED)
– 1–2 sentences max.
– Must reference only stats in the JSON with actual values.
– Prefer the highest-weight categories first (EPA, point differential, QBR), then mention one supporting efficiency stat if relevant.
– If missing inputs affected the model, explicitly mention which key(s) were missing (most important only).
REQUIRED SCREEN OUTPUT FORMAT (MUST FOLLOW EXACTLY)
– Output ONLY a markdown table (no intro text, no bullets, no extra commentary).
– The table must have exactly these columns in this order:
| Game | Pick | Conf (1-10) | Model Score (A-H / Max) | Key Edges | Missing Inputs |
Rules for each column:
– Game: “<away_team_code> @ <home_team_code> (<date>)”
– Pick: “<winner_team_code>”
– Conf (1-10): integer 1–10
– Model Score (A-H / Max): “A:<away_score> H:<home_score> / <max_points_available>”
– Key Edges: short phrase listing up to 3 strongest reasons based ONLY on present values, prioritized by weight:
– Example: “EPA, Point Diff, QBR”
– If fewer than 3 are available, list what you used.
– If none available, “Insufficient data”
– Missing Inputs: comma-separated missing keys (exact key names) or “None”
One row per matchup, sorted by matchup.date ascending, then game_id.
Begin now by reading the uploaded matchups.json.
Reality check: did this beat Vegas?
Hahaha. No.
Also, it was never designed to. Vegas is not using the same constraints I am. They have deeper datasets, better models, and market feedback loops.
What this did give me was better: a repeatable workflow for how I build prompt chains now. And that applies to real business work far more than it applies to winning a $1 weekly prize.
Business use cases where this pattern is immediately useful
Football is just a fun demo. The pattern is the product.
Anywhere you have messy inputs, a need to enrich with facts, and a need for a consistent output, prompt chaining helps. Examples:
- Data validation before reporting: Validate a CSV extract (schema, missing fields, duplicates), then enrich it (definitions, segment mapping), then generate a weekly summary.
- QA automation: Validate a dataset, run rule-based checks, output a pass/fail report with what to fix.
- Requirements intake: Turn stakeholder requests into structured JSON requirements, then generate a first draft spec.
- Ops triage: Validate inbound requests, enrich them with context, then route them with a confidence score.
- Executive updates: Enrich KPIs with context and deltas, then produce a standardized narrative that doesn’t change format every week.
The same three-step flow works:
Validate → Enrich → Decide
Lessons learned (the stuff I didn’t understand until I built this)
1) Web scraping limits are real, and “browsing” isn’t data engineering
The browsing step worked, but it wasn’t elegant. Some sites are easy: simple HTML tables, consistent stat pages, clean URLs. Others are… not. You’ll run into pages that are paywalled, blocked, dynamically rendered, or just structured in a way that’s hard for an automated tool to reliably extract. Even when a page is accessible, you’re often getting just enough content to answer a question, not a pristine dataset.
That’s why Prompt 2’s “null + warnings” behavior matters. If you treat browsing like a guaranteed pipeline, you’ll build something that breaks the first time a site changes a layout or decides it doesn’t want to allow automated access. Treat browsing like a best-effort convenience layer, not a source-of-truth ingestion system.
2) Token/context limits will force you to design like an engineer
This one cost me the most time early on. Long prompt chains can hit limits in two ways:
- Context window: the model can only process a maximum number of tokens in a single request, which includes your input plus the model’s output (and, depending on the system, internal reasoning tokens).
- Practical UI limits: in ChatGPT, you can run into “message too long” errors or outputs that get cut off. There isn’t one universally posted “ChatGPT message token limit” because it can vary by model and product surface, but the underlying constraint (finite context) is very real.
This is exactly why I shifted to writing the results back into matchups.json (instead of dumping everything to the screen), and why batching matchups became necessary in earlier iterations. The fix is designing outputs to be compact, structured, and re-usable across steps.
3) ChatGPT is better at analysis than it is at collecting data
The model shines when it’s reasoning over structured inputs: comparing teams, weighing tradeoffs, producing a consistent decision table, summarizing what matters.
It’s much weaker at being your web crawler.
In other words: if you can collect the data yourself (or you already have it in a CSV / warehouse / BI layer), do that. Then feed ChatGPT the clean dataset and let it do what it’s best at: interpretation, explanation, prioritization, and decision support. In a real business setting, you usually already own the data. The key is to leverage ChatGPT’s strengths: you hand it clean inputs and it outputs reliable analysis, which is the same reason prompt chaining (Validate → Enrich → Decide) is so effective.
And yes, I learned all of this while trying to win a football pool that paid out one dollar. That is a sentence I can’t believe I just typed.
Closing
I may not have won the pool, but I did build a mental model I now use constantly.
If you take one thing from this post, take this: stop writing prompts alone. Use ChatGPT to help you specify the workflow, ask the clarifying questions, lock in the contract, then iterate like you would with code.
Fortunately, winning the football pool was never really the point. Learning how ChatGPT works and building a repeatable way to use AI was.
Contact Us
If you would like to talk to someone at Maverick Data about maximizing your usage of the Sigma platform, please email us at spencer@maverickdata.io for more information!