Method

How this forecast is made

AI forecast, not a poll or an official result. Five models each return a Republican win probability for every Senate race and every House district. Election day is November 3, 2026. This page is the request they receive, and what the site does with the answers.

The panel

Each model is called on its own through OpenRouter. Live web search is on for every one of them. A call contains the system prompt and the race message only, so no model sees another's answer.

None of these models is marked as already searching the web, so each call adds :online to the model id.

  • GPT-6 Astra
  • Claude Fable 5.1
  • Gemini 4 Argon
  • Grok 4.7
  • DeepSeek V4 Pro

The system prompt

Every call starts with the same system prompt. The text in the box is that prompt, copied from the code. It is the same for every model and every race.

System prompt
You are one independent forecaster in a panel. Forecast only United States 2026 midterm races that have not been held yet. Election day is November 3, 2026.

Rules:
- The user message states today's date. If that date is on or after November 3, 2026, do not forecast. Return lean "not_forecastable" for each race and explain that the election is not still upcoming. Do not invent a result.
- Use the race facts in the user message. Do not invent candidates. If a name is marked uncertain, say so in the reasoning.
- The facts are a candidate list, not a prediction and not a rating. Ignore Cook, Sabato, Inside Elections, and any other published rating even if you find one. Give your own probability.
- Explain the reasoning for every race. Name the sources you actually used for that race. Every source needs a real http or https URL. If you do not have a URL, omit that source. Never invent a citation. If you are uncertain, say so.
- confidence is the probability from 0 to 100 that the party in lean wins.
- pRepublican is the probability from 0 to 100 that the Republican nominee wins.
- If lean is R, confidence must equal pRepublican. If lean is D, confidence must equal 100 minus pRepublican. If pRepublican is from 45 through 55, set lean to tossup and confidence to 50.
- Treat Republican and Democrat symmetrically. Do not argue for a party.
- Return only JSON. No markdown.

Schema for one race:
{"raceId":"string","lean":"R"|"D"|"tossup","confidence":0,"pRepublican":0,"reasoning":"string","keyFactors":["string"],"sources":[{"title":"string","url":"https://..."}]}

Return {"races":[ one object per requested raceId ]}. Keep each reasoning under 200 characters, at most 3 key factors, and at most 3 sources.

What each race message contains

The second message states today's date in US Pacific time and names the races to forecast. A live run uses the Pacific date of that run. The examples further down use the date written on the race file, so the first line shows that date and the rest shows the race facts.

The message does not include polls, past election results, or a published rating. The system prompt tells the model to ignore Cook, Sabato, Inside Elections, and any other published rating, and to give its own probability.

The race file says:

Candidate names are transcribed from the cited public pages. They are not a certified ballot and they are not a rating. House nominees are intentionally absent.

Senate

A Senate call is one race. The message includes the race id, the state, the chamber, the class, whether the race is special, the term note, the incumbent when the file lists one, the candidates, the notes, and the source URLs stored on that race. An incumbent or a candidate is a name, a party, and a certainty. The incumbent also includes a status.

House

A House call is one state, at most 5 districts. The message names the state and the district number, or at large, and includes the note stored on that district. It sets nomineesInDataFile to null. It does not name an incumbent, a nominee, or a party. The model is told that the data file does not name House incumbents or nominees, to use current public sources, and to say when a name is uncertain.

Senate message for Maine, using the race file date 2026-10-28
Today is 2026-10-28 in US Pacific time. The election is November 3, 2026 and is still upcoming only if today is before that date.

Forecast this Senate race if it is still upcoming. Use raceId "senate-me".

{
  "raceId": "senate-me",
  "state": "Maine",
  "chamber": "senate",
  "class": 2,
  "special": false,
  "term": "Six-year term beginning January 3, 2027.",
  "incumbent": {
    "name": "Susan Collins",
    "party": "Republican",
    "status": "renominated",
    "certainty": "listed"
  },
  "candidates": [
    {
      "name": "Susan Collins",
      "party": "Republican",
      "certainty": "listed"
    },
    {
      "name": "Troy Jackson",
      "party": "Democratic",
      "certainty": "listed"
    }
  ],
  "notes": "The page intro says Graham Platner was replaced as the Democratic nominee by Troy Jackson. The summary table lists Troy Jackson.",
  "factSources": [
    "https://en.wikipedia.org/wiki/2026_United_States_Senate_elections"
  ]
}
House message for 5 Washington districts, using the race file date 2026-10-28
Today is 2026-10-28 in US Pacific time. These House elections are November 3, 2026. Forecast them only if today is still before that date. Otherwise mark every race not_forecastable.

Forecast only this group of 5 Washington districts. Do not forecast other districts in Washington. Return one object for each raceId below.

The data file does not name House incumbents or nominees. Use current public sources for the candidates, and say when a name is uncertain.

[{"raceId":"house-wa-01","state":"Washington","district":1,"nomineesInDataFile":null,"note":"Seat count follows the 2020 Census apportionment. This file does not name a 2026 House incumbent or nominee. Several states redrew districts before 2026, so a 2024 incumbent list would mis-state these districts."},{"raceId":"house-wa-02","state":"Washington","district":2,"nomineesInDataFile":null,"note":"Seat count follows the 2020 Census apportionment. This file does not name a 2026 House incumbent or nominee. Several states redrew districts before 2026, so a 2024 incumbent list would mis-state these districts."},{"raceId":"house-wa-03","state":"Washington","district":3,"nomineesInDataFile":null,"note":"Seat count follows the 2020 Census apportionment. This file does not name a 2026 House incumbent or nominee. Several states redrew districts before 2026, so a 2024 incumbent list would mis-state these districts."},{"raceId":"house-wa-04","state":"Washington","district":4,"nomineesInDataFile":null,"note":"Seat count follows the 2020 Census apportionment. This file does not name a 2026 House incumbent or nominee. Several states redrew districts before 2026, so a 2024 incumbent list would mis-state these districts."},{"raceId":"house-wa-05","state":"Washington","district":5,"nomineesInDataFile":null,"note":"Seat count follows the 2020 Census apportionment. This file does not name a 2026 House incumbent or nominee. Several states redrew districts before 2026, so a 2024 incumbent list would mis-state these districts."}]

What each model returns

The model returns JSON only: one object with a races array, and one object per requested race id. Each race has a Republican win probability from 0 to 100, a lean of R, D, or tossup, a confidence, reasoning, key factors, and sources. The prompt asks for reasoning under 200 characters, at most 3 key factors, and at most 3 sources. Every source needs a real http or https URL. A source with no URL is supposed to be omitted, and a citation is not to be invented.

The probability decides the lean. Above 55 the lean is Republican. Below 45 the lean is Democratic. From 45 through 55, inclusive, the lean is a toss-up. The prompt asks for confidence equal to the Republican probability on an R lean, equal to 100 minus that probability on a D lean, and equal to 50 on a toss-up.

The site reads a reply that is wrapped in a code fence. A race is dropped when the race id is unknown or repeated, the reasoning is shorter than 20 characters, or the lean or the percent cannot be read. A lean of not_forecastable is skipped. A value strictly between 0 and 1 is read as a fraction of 1. If the Republican probability is missing, it is filled in from the lean and the confidence. If the lean and the probability disagree, the probability wins: the lean is recomputed from the cutoffs above, and the confidence is recomputed from the probability. On a toss-up, the stored confidence is the rounded larger of the Republican probability and 100 minus that probability.

The reader keeps at most 6 key factors and 4 sources, even though the prompt asks for 3. On the race card, a toss-up stays labeled Toss-up. Above a toss-up, the favored side is Lean below 70, Likely from 70, and Safe from 85. The reasoning list shows repeated factors first, then a model's first sentence if there is still room, up to 4 lines.

How the call is sent

The first try asks for a JSON object and sets reasoning effort to low. The request does not set temperature, top_p, or a seed, so those stay at the provider's defaults.

If that try fails, the same search-enabled model is asked again without JSON mode and without a reasoning effort. If the first or second response is HTTP 404, one more try uses the model id without :online, OpenRouter's web plugin with at most 5 results, and JSON mode with low reasoning effort again. A retry that drops web search after HTTP 400 or 504 is in the code and stays off unless it is switched on.

The race file lists 35 Senate races and 435 House districts. House districts are grouped by state, at most 5 per call, which is 110 groups. A full pass, before any retry, is 175 Senate calls and 550 House calls.

Endpoint
https://openrouter.ai/api/v1/chat/completions
Messages
System prompt, then the race message. No other model's answer.
Web search
On. :online is added to the model id.
JSON mode
On for the first try, and again if a 404 retry is needed.
Reasoning
Low effort on the first try, and again if a 404 retry is needed.
Temperature
Not set. Provider default.
Seed
Not set. Provider default.
Senate tokens
Up to 2,500.
House tokens
1,600 plus 1,100 per district, capped at 12,000. 5 districts is 7,100.
Timeout
90 seconds for the call, including the tries inside it.
Job retries
A unit is asked at most 3 times. A later ask includes only races that model has not answered.
At once
Up to 4 calls.
Source checks
Up to 6 URLs in a run are requested and marked verified or unverified. A title with no usable URL can still be shown, marked unverified.

How the answers become the forecast

  • 10,000Simulated elections
  • 0.35Shared swing, log-odds
  • 20261103Fixed seed
  • 90%House coverage before odds

For each race, the published Republican probability is the plain average of the models that answered. Each of those models counts the same. The average is rounded to the nearest percent, and the lean is read from that percent with the same 45 and 55 cutoffs. A model that missed the race is left out. The low and the high on the card are the lowest and highest of the probabilities that were averaged.

The race is flagged when the models' leans are not all the same, or when the high and the low are 20 points or more apart. Share text then says the models disagree. The factors on the card are the ones models repeated most often, up to 4. Sources are merged, up to 8.

Chance of control, expected seats, and the 80% range come from 10,000 simulated elections. The seed is 20261103, so the same snapshot gives the same odds. Each draw adds one shared national swing to every race, then flips each seat. The swing is a normal draw with mean 0 and standard deviation 0.35, added to the log-odds of the probability. A positive swing moves every race toward the Republican by the same amount. The prompt tells the model to treat Republican and Democrat the same way, and a swing with mean zero does the same in the simulation. Before the swing, a probability is clipped to 0.1% through 99.9%, so a near-certainty can still move.

Expected seats are the median Republican total across the draws. Democratic seats in that tally are the ones that were not called Republican. Independent seats that are not on the ballot stay independent. The 80% range is the 10th percentile to the 90th percentile of Republican seats.

Senate control adds the seats that are not on the ballot: 31 Republican, 32 Democratic, and 2 independent. Angus King and Bernie Sanders caucus with the Democrats, and the simulation counts independents with the Democrats.

A 50-50 Senate goes to the Vice President's party. The holdover file lists JD Vance, a Republican, through January 20, 2029. 50 Republican seats is a Republican majority. The marker on the seat bar still sits at 51, a majority of 100.

House control is 218 of 435. Senate control odds stay hidden until every Senate race has a forecast. House control odds stay hidden until at least 90% of the 435 districts have a forecast. After that, a district with no forecast is drawn at 50% under the same swing. That 50% is an assumption, used only for those odds.

The swarm seat card uses those simulated medians. A single model's seat card counts that model's own calls: Republican when its probability is 50 or higher, and Democratic when it is lower. Races that model did not answer are left out. When it answered at least one Senate race, the seats that are not on the ballot are added. That model's chance of a Senate majority is the same simulation on its own probabilities, and it is shown only when the model answered every Senate race.

What this is, and what it is not

The snapshot stores each model's probability, lean, confidence, reasoning, factors, and sources. It does not store the prompt again. The prompt can be rebuilt from the templates and the race file, which is what the examples on this page do.

  • AI forecast, not a poll or an official result.
  • A model can be wrong. It can disagree with the others. A page it cites can be stale, and a URL can stay unverified.
  • House names in a model's answer come from that model's own search. Names on a House card come from the site's nominee file. Photos come from the site's portrait file. Those card names are not in the message the model received.
  • Some races have fewer than 5 answers. The average uses the models that answered.
  • Candidate names in the race file are a list for the prompt. They are not a certified ballot and they are not a rating.