Forecast Categorical and Threshold Questions
Not every question is yes or no. "Who wins?" has four answers. "How high does oil go?" has a ladder of them. Running each option as its own binary forecast produces probabilities that do not add up, because each run researches the question in isolation and none of them sees the others.
categorical and thresholded forecasts research all the options together in a single pass, so the probabilities come back coherent.
| Type | Question shape | Constraint |
|---|---|---|
categorical | Which one of these outcomes? | Options must be mutually exclusive and exhaustive. Probabilities sum to exactly 100. |
thresholded | How far past each threshold? | One quantity, several cut-offs, ordered least strict to most strict. Probabilities do not sum to 100. |
Both require effort_level="HIGH", which is the default. Both take their options from a named input column holding a JSON array, not from the question text.
Categorical: exhaustive or nothing
The exhaustiveness rule matters more than it looks. If your options are "Anthropic", "OpenAI" and "Google", and the real answer might be Meta, the probabilities are forced to sum to 100 across a set that excludes the truth, and every number is inflated. Add an explicit "Other" option whenever the list could be incomplete.
Two to fifty options are allowed, and they must be unique.
Worked examples we have published:
- How many of four SpaceX bull-case milestones land?
- How many named effort levels will the Gemini 3.5 Pro API expose?
- How will Google ship its highest-capability Gemini 3.5-era model?
Thresholded: one quantity, several bars
Thresholds are for questions like "above $80, above $90, above $100". They are not mutually exclusive, so the probabilities are nested rather than summing to 100. Order them least strict to most strict and expect a monotonically falling series.
One threshold on its own is really a yes/no question. Use forecast_type="binary" for that and get a clean probability column instead.
Add FutureSearch to Claude Code if you haven't already:
claude mcp add futuresearch --scope project --transport http https://mcp.futuresearch.ai/mcp
Give Claude the options explicitly and it will build the array column:
Forecast how Google will deliver its highest-capability Gemini
3.5-era model as of 31 December 2026. The options are: a distinct
Ultra-tier model, Deep Think as the top tier, Pro as the top tier,
or something else.
Tool: futuresearch_forecast
├─ data: [{"question": "How will Google deliver its highest-capability...",
│ "options": ["Distinct Ultra-tier model",
│ "Deep Think is the top tier",
│ "Pro is the top tier",
│ "Other"]}]
├─ forecast_type: "categorical"
├─ categories_field: "options"
└─ effort_level: "HIGH"
→ Submitted: 1 row for categorical forecasting.
Add the FutureSearch connector if you haven't already. List the options in the message and say that they are exhaustive:
Forecast which of these four outcomes happens. Treat the list as exhaustive and include an "Other" bucket.
Go to futuresearch.ai/app and spell out the options:
Forecast the probability of each outcome: Anthropic first, OpenAI first, neither by end of 2027.
For a batch, upload a CSV where one column holds each row's options as a JSON array.
pip install futuresearch
export FUTURESEARCH_API_KEY=your_key_here # Get one at futuresearch.ai/app/api-key
Categorical. The options live in an input column as a JSON array of strings, named by categories_field:
import asyncio
import json
import pandas as pd
from futuresearch.ops import forecast
races = pd.DataFrame([
{
"question": (
"As of 31 December 2026, how will Google deliver its single "
"highest-capability Gemini 3.5-era model?"
),
"options": json.dumps([
"A distinct Ultra-tier model",
"Deep Think is the top tier",
"Pro is the top tier",
"Other",
]),
},
])
async def main():
result = await forecast(
input=races,
forecast_type="categorical",
categories_field="options",
effort_level="HIGH",
)
for row in result.data.itertuples():
print(json.loads(row.probabilities))
asyncio.run(main())
probabilities comes back as a JSON object mapping each outcome to its probability, summing to exactly 100, alongside a shared rationale.
Thresholded. Same shape, but the array holds cut-offs and is named by thresholds_field:
oil = pd.DataFrame([
{
"question": "What will Brent crude peak at during 2027, in USD?",
"levels": json.dumps([80, 90, 100, 120]),
},
])
result = await forecast(
input=oil,
forecast_type="thresholded",
thresholds_field="levels",
effort_level="HIGH",
)
probabilities maps each threshold to the probability of clearing it. Expect the series to fall as the thresholds get stricter.
Common mistakes
Options that overlap. "Under $100" and "Under $120" are not mutually exclusive, so they are thresholds, not categories. Picking the wrong type here is the most common error, and the sum-to-100 constraint will quietly distort the answer.
Forgetting "Other". If you can imagine a headline that satisfies none of your options, the list is not exhaustive.
Fifty near-identical buckets. The limit is fifty, but resolution beyond about ten options is usually noise. Coarser buckets give you numbers you can act on.
Built with FutureSearch. See the forecast documentation for all parameters and output formats. Related guides: Write Resolution Criteria That Hold Up, Forecast Conditional Scenarios, Forecast Outcomes for a List of Entities.