From Natural Language to a Probability Predictor: Modeling a Football Tournament With Elo and Monte Carlo Simulation
This article walks through building a football tournament champion probability predictor that can be generated from a single natural-language prompt, then shows how to turn that prototype into a reliable service. It breaks the problem into a three-layer model—Elo ratings to quantify team strength, a double Poisson model to convert rating gaps into scorelines, and Monte Carlo simulation to estimate outcome probabilities across a full tournament—and explains how to calibrate and validate each layer. It also covers AI-assisted development in practice: writing constraint-driven prompts, generating in verifiable stages, auditing common defects in generated code, and validating results through backtesting, log loss, Brier score, and calibration curves rather than intuition. Finally, it examines the cost structure of multi-turn generation and the engineering work required to productionize the prototype.
1. Starting From a Case: A Predictor From a Single Sentence
Recently, the same experience has been showing up across technical communities. A user types a one-line request into an AI coding tool: “Build a 2026 football tournament champion probability predictor using Elo ratings plus Monte Carlo simulation.” Then they go make a cup of tea. By the time they are back, a tool that simulates the full schedule for all 48 teams, produces score distributions, and even generates a share link is already sitting in a local directory. The model calls for that whole generation run cost on the order of a dozen or so dollars. A common reaction in the discussions: the cost is slightly higher than a takeout meal, and if a person had built it, setting up the whole framework would typically take a weekend.
This is the typical shape of generative coding today: you describe the requirement in natural language, and runnable code appears within minutes. But it also introduces a new engineering problem: when the time to write code shrinks from “weeks” to “minutes,” the reason a project fails is no longer typing speed—it is whether the requirements were thought through, whether the model’s assumptions are correct, and whether the results were validated.
This is especially true for probability-prediction applications. Their value lies not in lines of code but in three layers of decisions:
- Are the modeling assumptions correct: can ratings be reasonably converted into scorelines, and scorelines into match outcomes;
- Can the computation support the scale: will tens of thousands of full-tournament simulations finish, and fast enough;
- Are the results trustworthy: do the probabilities converge stably, and can they be backtested and calibrated against historical data.
Using this case as a thread, this article unpacks the full technical pipeline behind “one-sentence generation” and lays out the engineering methods for polishing a prototype into a reliable service.
2. Breaking Down the Requirement: What Should the Predictor Output?
Before generating any code, break the requirement into items you can verify. A solid tournament probability predictor usually answers these questions at once:
- Championship probability: how likely each team is to lift the trophy;
- Stage probability: the probability of advancing from the group, reaching the final, making the semifinals, and so on;
- Score distribution: the win/draw/loss probability of a key match, plus the frequency of each scoreline;
- Uncertainty: how sensitive the model is to different ratings, and whether the number of simulations is already enough.
These outputs map to three design decisions you must pin down:
- How to quantify strength: describe each team’s strength with a unified rating system;
- How ratings become outcomes: how scorelines are generated, and what rules apply to the group stage versus the knockout stage;
- How outcomes aggregate into probabilities: use enough repeated simulations so that frequency approaches probability.
Hand these three points to a generative tool as part of the prompt, and the resulting code will be noticeably closer to usable. Let us go through them one by one.
3. The Modeling Core: A Three-Layer Structure From Ratings to Outcomes
3.1 Layer 1: Elo Ratings, a Common Yardstick for Team Strength
Elo ratings began as the ranking system in chess and were later widely adopted for strength assessment in team sports such as football and basketball. The core idea: each team has a rating value, and the gap between two teams determines the expected result. The common scale is that a 400-point gap makes the stronger side’s expected win rate about 10 times higher.
In the football version of Elo that only counts win/draw/loss, team A’s expected score against team B (win = 1 point, draw = 0.5, loss = 0) is:
E_A = 1 / (1 + 10^(-(R_A - R_B) / 400))
Take the sample data: Japan is rated 1906 and faces the Netherlands at 1948, a 42-point gap; plugged in, the expected score is about 56%. The standout advantages of Elo are:
- Comparable: different teams’ ratings sit on the same scale;
- Portable: every match result adjusts the ratings in reverse according to the rules, and they update iteratively over time.
But two boundaries are worth noting when using it:
- Elo reflects long-term strength and does not automatically capture form or squad changes. International matches are a sparse sample, so short-term factors like injuries and coaching changes often need manual adjustment or an extra form parameter to compensate.
- Elo is a relative quantity, not an absolute one. A 42-point gap corresponds to roughly a 56% expected score whether it happens between top teams or between mid-table teams. So comparisons are only valid within the same rating system.
3.2 Layer 2: The Double Poisson Model, Translating the Strength Gap Into Goals
Elo only answers “who is more likely to win,” but predicting outcomes also requires scorelines. Goals in a football match are a classic rare counting event, and the Poisson distribution is precisely the classic model for the number of rare events in a given interval. If a team averages μ goals ignoring the opponent’s strength, then against a stronger opponent its expected goals shift with the rating gap.
A common engineering-friendly formulation is:
d = (R_A - R_B) / C # C controls how sensitive the rating gap is to the goal gapλ_A = μ × 10^(d / 2)λ_B = μ × 10^(-d / 2)
Here λ_A and λ_B are the expected goals for teams A and B, drawn from a Poisson distribution; μ is the baseline goal expectation when the two sides are evenly matched; and C is the sensitivity parameter—the smaller it is, the more the strength gap affects the score.
This form has an important property: λ_A × λ_B = μ², independent of the strength gap. In other words, the strength gap only changes the relative ratio of the two sides’ goals; it does not inflate the total goal expectation without bound. This avoids the problem you get when converting directly with 10^(ΔR/400), where a strong team’s expected goals could be ten times a weak team’s, producing unrealistic scorelines.
| Rating gap ΔR | Goal expectation (μ=1.4, C=400) | Win / Draw / Loss (weak side’s view) |
|---|---|---|
| 0 | 1.40 / 1.40 | ~26% / 26% / 48% |
| 42 | 1.59 / 1.23 | ~36% / 25% / 39% |
| 100 | 1.91 / 1.03 | ~52% / 23% / 25% |
| 200 | 2.49 / 0.79 | ~70% / 17% / 13% |
Computed with μ=1.4 and C=400, this set of numbers shows the pattern directly:
- A 42-point gap barely shows on the scoreline: win/draw/loss is about 39% : 25% : 36%. Football’s low-scoring nature amplifies randomness, so the strong team’s edge is not as large as you might imagine.
- A 200-point gap is what visibly pulls the scoreline apart.
- Even when teams are evenly matched, the outcome is not symmetric: two equal teams, by the double Poisson model, give A win / draw / B win of about 39% : 26% : 35%. A draw is the most likely single scoreline (for example 1:1), but once the three outcomes are aggregated, either side winning is actually more likely.
So neither μ nor C should be set out of thin air; both must be calibrated against historical match data, and maximum likelihood estimation is an available method. For example, tune the parameters so the model’s win/draw/loss probabilities match the historical actual frequencies as closely as possible, and keep the total goal expectation near the tournament’s historical average.
3.3 Layer 3: Monte Carlo Simulation, Approximating Probability Through Many Repeated Trials
A single match’s outcome cannot be analytically derived into a whole-tournament championship probability, because the schedule structure is too complex. The most direct approach is to simulate the whole tournament many times and estimate probability from frequency—this is the Monte Carlo method.
Take the expanded 2026 format as an example:
- Group stage: 48 teams split into 12 groups of 4, each a single round-robin, for 72 matches;
- Advancement rule: the top two of each group plus the 8 best third-placed teams, 32 teams in total, reach the knockout stage;
- Knockout: the Round of 32, Round of 16, quarterfinals, and semifinals in sequence, plus a third-place match and the final, for 32 matches;
- Total matches: 104;
- Champion path: a team needs to play at least 8 matches to go from the group stage to the title.
So each team’s championship probability is essentially “the number of times it wins the title across all random paths ÷ the total number of simulations.”
4. Sample Code: Wiring the Three Layers Into a Full Simulator
The skeleton below shows how to organize the three layers into a runnable simulator. The core idea: describe “a single match” through a unified interface, so the group stage and knockout stage share the same scoreline-generation logic and differ only in how ties are handled.
import mathimport numpy as npfrom dataclasses import dataclass, field@dataclassclass Team:name: strrating: float # Long-term strength rating from a public rating sourcegroup: str = ""stat: dict = field(default_factory=lambda: {"pts": 0, "gf": 0, "ga": 0, "gd": 0})def expected_goals(r_a: float, r_b: float,mu: float = 1.4, c: float = 400.0) -> tuple[float, float]:"""Derive each side's goal expectation from the Elo rating gap; the product is always mu², avoiding an inflated total"""d = (r_a - r_b) / creturn mu * 10 ** (d / 2), mu * 10 ** (-d / 2)def poisson_sample(lam: np.ndarray, rng: np.random.Generator) -> np.ndarray:"""Vectorized Poisson sampling: generate goals for all matches to be simulated at once"""return rng.poisson(lam)def play_match(a: Team, b: Team, rng, knockout: bool = False) -> tuple[int, int]:# Neutral venue assumption: knockout matches at major tournaments usually have no home side; group matches are also treated as neutral# To distinguish home and away, add a fixed home advantage to the home team's ratingla, lb = expected_goals(a.rating, b.rating)ga, gb = poisson_sample(np.array([la, lb]), rng)ga, gb = int(ga[0]), int(gb[0])if not knockout or ga != gb:return ga, gb# Knockout tie: extra-time goal expectation is usually lower, so resample after scaling by a factorea, eb = expected_goals(a.rating, b.rating, mu=1.4 * 0.3)while ga == gb:ga, gb = poisson_sample(np.array([ea, eb]), rng)ga, gb = int(ga[0]), int(gb[0])if ga == gb: # still level, go to a penalty shootoutpa = rng.random() < 1 / (1 + 10 ** (-(a.rating - b.rating) / 400))return ga, gb, int(pa) # winner indicated by a third return valuereturn ga, gb, 1 if ga > gb else 0
The above is the main structure. A few more points:
- Scoreline generation: ties must be resolved in the knockout stage. Extra time usually has a lower goal expectation, so you can resample after scaling μ by a factor less than 1; a penalty shootout can be approximated as a Bernoulli trial whose probability is decided by the ratings.
- Points and ranking: in the group stage, a win, draw, and loss are worth 3, 1, and 0 points. Ranking compares points, then goal difference, then goals scored, and if still tied, head-to-head record. The 12 third-placed teams are ranked together, and the top 8 advance. It is best to write these details as standalone functions so they are easy to adjust to the latest regulations.
- Knockout bracket: after expansion, the Round of 32 is made up of 12 group winners, 12 group runners-up, and the 8 best third-placed teams. Which half of the bracket a given third-placed team enters depends on its group, and the exact mapping should follow the tournament regulations rather than being hard-coded on a guess.
It is worth stressing: the code above only shows the model skeleton and omits data-source loading, schedule orchestration, result aggregation, and error handling. When you use the full code a generative tool produces, these are exactly the parts that need the most review.
5. From Code to Product: Generative Development in Practice
5.1 Write the Prompt Clearly: What the Model Needs Is Constraints, Not Adjectives
A request like “build a World Cup predictor” leaves the model too much decision space. A more effective prompt should include:
- Input: rating data format, field meanings, and source notes;
- Model: the Elo expected-score formula, the double Poisson mapping, and parameter ranges;
- Format: grouping, advancement rules, and knockout tie handling;
- Output: the definition of each probability, and whether the random seed is fixed;
- Engineering constraints: available language and dependencies, per-run performance targets, and whether a UI is needed.
5.2 Generate in Stages: Every Step Has a Verifiable Milestone
Rather than having the model produce all the code at once, generate in five layers—data, model, schedule, output, UI—and verify each step with a minimal test case, for example:
- Given two teams whose ratings differ by 400, is the expected win rate about 9:1;
- Simulate 1,000,000 matches with an expected 1.4 goals—does the average come out near 1.4;
- Does one round-robin group’s total points equal 18 (6 matches × 3 points).
5.3 Focus Your Review on Seven Common Defects in Generated Code
- Missing tie handling: a knockout tie is decided directly, or the code falls into an infinite loop;
- Wrong format details: advancement rules, third-place ranking, or bracket mapping that do not match the real regulations;
- Non-reproducible randomness: the random seed is not fixed, so every run differs and problems cannot be traced;
- Insufficient numerical stability: coefficients too large cause overflow, or loops run too long and slow the whole run;
- No input validation: missing ratings or outliers are not handled and directly pollute the results;
- Interface and config errors: calling an endpoint that does not exist, or hard-coding data-source URLs and keys into the code;
- No tests or error handling: the program crashes outright when data loading fails or a parameter is invalid.
The corresponding principle: anything that can be verified with a test assertion should not rely on “reading the code” alone.
5.4 Validating Results: Trustworthy Matters More Than “It Runs”
For a probability model, the most common validation mistake is comparing results against betting odds and, on finding they “roughly match,” assuming the model is reliable. Odds themselves contain market expectations and operational factors and can only serve as a rough reference. A more rigorous approach is backtesting:
- Historical backtest: roll the rating data back to a pre-match point in time, simulate past tournaments, and check how well champions and semifinalists were hit;
- Scoring rules: use probabilistic metrics such as log loss and the Brier score, not just whether the ranking was guessed right;
- Calibration curve: when the model gives a 30% probability, does that outcome really occur about 30% of the time in the long run;
- Sensitivity analysis: nudge parameters C and baseline μ and watch whether the top-five probability ranking stays stable. If the conclusions swing sharply with a small parameter change, the results are too sensitive to the assumptions and should be published cautiously.
6. Interpreting Results: Sample Simulation Output and Three Observations
Suppose the rating data comes from a public football rating source, and 10,000 simulations give these sample results:
- Spain (rating 2157): about 31.9%;
- Argentina (2115): about 17.8%;
- France (2063): about 9.2%;
- Brazil (1991): about 6.6%;
- England (2021): about 6.0%.
The top five total about 70%, and the other 43 teams share about 38%. This structure roughly matches mainstream market expectations. But the simulation also surfaces a few points worth expanding on.
6.1 Colombia: A Third-Tier Powerhouse Overlooked by Intuition
Colombia is rated 1982, ranked 7th in the world—higher than traditional powers like the Netherlands and Germany. Yet in many people’s verbal predictions, they rarely appear in the title conversation. From the model’s perspective, their group is low-pressure, and once they reach the knockout stage, the strength their rating reflects is enough to carry them further.
This points to something: there is a systematic gap between “fame” and “rating.” Public intuition before a major tournament is often dominated by recent tournament results and star attention, whereas the rating system reflects longer-term, more stable accumulation of strength. One of the model’s values is to quantify this gap.
6.2 Germany: The Weight of the Draw Is Underrated
Germany is rated 1932, and their group contains an opponent rated 1938. In other words, their opening group match is effectively an even, tough game. Compare another group: Canada’s opponents are rated around 1600 and 1430, so their path out is clearly easier.
This raises a model-level observation: the strength of a group’s opponents indirectly affects later stages through “physical drain, goal-difference accumulation, and the advancement path.” A basic model often treats each match as an independent event, so if you want to capture the draw effect, you need to introduce state variables—for example, letting a team’s rating decay after a run of high-intensity matches.
6.3 Japan and the Asian Landscape: Tiers Behind the Rating Gap
Japan is rated 1906, around 14th in the world and clearly ahead within Asia. Among same-continent teams, Iran is about 1772 and South Korea about 1758, so Japan leads by roughly 130 to 150 points. Converted to expected score, Japan is about 69% against Iran and about 72% against South Korea.
This does not mean there is no drama between the two matches. By the double Poisson estimate, the draw probability for Japan vs. Iran is still around 25%, so matches between Asian powers remain high-variance events.
6.4 Two “42-Point” Easter Eggs
There is an interesting detail in the case: both Japan vs. the Netherlands and Spain vs. Argentina—two marquee matchups—have a rating gap of about 42 points, and both imply an expected win around 56%.
But the two mean different things:
- Japan vs. the Netherlands: one side is a regional top, the other a world top. A 56% expected score belongs to a match with a very wide “upset window.”
- Spain vs. Argentina: both sit at the top of the rating system, and 42 points is only a fine margin “within the peak.” Given the weight of a final, this match’s outcome distribution is closer to a coin flip.
The same gap does not have an equivalent impact on the tournament’s trajectory across different rating bands. This also shows that a probability predictor should not output just a single number; it is better to also give the score distribution, so users can see structural information like “draw share” and “one-goal-win share.”
7. Cost and Efficiency: Where a Session’s Spend Goes
7.1 Cost Structure: Three Billing Components, One Deciding Factor
In the case, one “one-sentence generation” cost model calls on the order of a dozen or so dollars. From a billing standpoint, the spend is mainly three parts:
- Input context: the requirement description, existing project code, and execution results returned by tools all consume input tokens;
- Output content: generated code, explanations, and fixes count as output tokens;
- Interaction rounds: every time the model reads files, runs tests, and fixes based on errors, it re-carries the context.
For mainstream model services, output tokens usually cost more per unit than input tokens. So the key variable driving cost is not the volume of code itself but the number of interaction rounds and the context length. In repeated trial-and-error, the same context is billed over and over, and the cost can balloon quickly.
7.2 Cost Optimization: Making the Model Make Fewer Mistakes Is Cheaper Than Making It Do More Work
- State constraints up front: put formulas, data formats, and format details into the first prompt to cut down on back-and-forth confirmation;
- Plan before code: have the model first output a modeling plan and data-flow description, get human confirmation, and only then generate the implementation, avoiding directional rework;
- Use tests instead of trial-and-error: prepare runnable acceptance cases so the model fixes against assertions, rather than repeatedly running and guessing from output;
- Generate in stages: carry only the necessary context at each stage, so irrelevant code does not consume tokens;
- Estimate by token usage: when estimating cost, base it on total “input + output” tokens and unit price, not on “how many times it generated.”
7.3 Efficiency and Experience: Make Results Explainable, Reproducible, and Shareable
- Reproducible results: fix the random seed and record the rating snapshot, so the same input yields the same result;
- Explainable results: show the score distribution of key matches in the UI, not just the championship probability;
- Shareable results: when generating a read-only result link, include only public data, and no sensitive config such as data-source URLs or keys.
8. From Prototype to Service: Engineering and Launch Checklist
To turn this prototype into a service that runs sustainably, you typically need to fill in these pieces:
- Data ingestion and updates: organize the public rating source into structured data, pull updates on a schedule, and validate missing and outlier values;
- Compute task management: tens of thousands of simulations are compute-intensive, so run them asynchronously in a separate task queue rather than blocking the main service;
- Result caching and versioning: record every simulation’s parameters, data, and random seed to form a traceable result version;
- Observability: set up monitoring and alerts for task duration, success rate, and error rate to catch data anomalies or out-of-range parameters early;
- Elasticity and cost control: scale simulation tasks on demand and release resources when idle. For example, compute tasks can be deployed on a container platform and auto-scaled by task concurrency; rating data and simulation results are well suited to object storage, which eases version tracing and sharing; runtime logs can be routed to a logging service and combined with monitoring alerts to catch anomalies early. Baidu AI Cloud offers general-purpose cloud capabilities such as containers, storage, logging, and monitoring, and can serve as one deployment option for building this kind of application.
9. Summary: Treat “One-Sentence Generation” as a Starting Point, Not the Finish Line
Back to the opening scene: one sentence, the time to make a cup of tea, and a working tool. It shows that generative coding has already lowered the barrier from “idea to prototype” to a very low level. But what really determines project quality is still whether the modeling assumptions hold, whether the results have been validated, and whether the engineering can withstand going live.
If you want to build something similar with an AI coding tool, here is a checklist to follow:
- Define the output first: pin down which questions you want to answer before deciding which model to use;
- Use an explainable model: the Elo-plus-Poisson structure is transparent and easy to debug, making it a good baseline;
- Calibrate instead of guessing: estimate parameters from historical data rather than setting them by experience;
- Backtest instead of eyeballing: measure model quality with log loss, the Brier score, and calibration curves;
- Test instead of assume: write the tournament rules as assertions so every step of the generated code is verifiable;
- Constrain interactions with cost awareness: state constraints up front and cut out useless rounds.
The ability to “generate a product from one sentence” is pushing developers from “people who write code” toward “people who define problems and validate results.” Modeling ability, data literacy, and engineering judgment are, in fact, the most durable parts of this shift.
