Forecasting the outcomes of a decision
decision() forecasts one outcome under each option of a decision. Each row is one outcome question, a column lists the options, and each option is forecast as its own hypothetical; the options are researched together. The decision can be yours or anyone else's.
The call
import asyncio
from pandas import DataFrame
from futuresearch.ops import decision
async def main():
result = await decision(
input=DataFrame([
{
"question": "What will this household spend in total on home energy, including any installation costs, from 2027 through 2036?",
"option": [
"Install a heat pump next year, taking the available grant",
"Run the gas boiler until it fails, then replace it with whatever is standard then",
],
},
]),
context=(
"The household owns and lives in a 142 square metre semi-detached house built in "
"1996 in Bad Nauheim, Hesse, and in 2025 it paid about 2,500 euros for 21,400 "
"kilowatt-hours of gas and about 1,330 euros for 3,900 kilowatt-hours of "
"electricity. Its gas condensing boiler was installed in 2004 and has needed two "
"repairs since 2023, and a heating firm in nearby Friedberg quoted 34,200 euros in "
"August 2026 for an air to water heat pump with a hot water cylinder and three "
"larger radiators."
),
alternatives_field="option",
forecast_type="numeric",
output_field="energy_spend",
units="EUR",
)
print(result.data[["percentiles", "rationale"]])
asyncio.run(main())
The output
{
"percentiles": {
"Install a heat pump next year, taking the available grant": {
"p10": 43330, "p25": 48330, "p50": 53330, "p75": 59500, "p90": 67000
},
"Run the gas boiler until it fails, then replace it with whatever is standard then": {
"p10": 45170, "p25": 52670, "p50": 61500, "p75": 71170, "p90": 84330
}
},
"rationale": "Installing the heat pump next year costs an estimated €53,330 at the median over the 2027-2036 window, structurally outperforming the €61,500 median cost of running the gas boiler to failure. ..."
}
percentiles maps each option to its own distribution and rationale explains the differences; for a binary outcome the column is probabilities, one probability per option. The values need not sum to 100 or move in order. See the published run.
Context
context is one string for the whole call. Put in it what the forecaster cannot look up about who is deciding, such as size, money, dates and constraints, never a number from an earlier forecast, and nothing that is public.
result = await decision(
input=DataFrame([
{
"question": "How many paying subscribers will the company have twelve months from now?",
"price": [
"Leave the price at $20 a month",
"Raise the price to $29 a month for new customers only",
"Raise the price to $29 a month for everyone, including existing subscribers",
],
},
]),
context=None,
alternatives_field="price",
forecast_type="numeric",
output_field="paying_subscribers",
units="subscribers",
)
Holding the price comes back p10 to p90 of 660 to 22,000 subscribers: the run without context.
The same call, with two sentences about the company:
context=(
"Lessonpost is a scheduling, lesson notes and invoicing web app for independent "
"music teachers, launched in March 2024, and in September 2026 it has 1,840 "
"subscribers paying 20 dollars a month, which is 36,800 dollars of monthly "
"recurring revenue. Gross subscriber churn has averaged 3.4 percent a month over "
"the past year and about 95 new paid subscriptions start each month."
),
Holding the price now comes back 1,900 to 2,480: the run with context.
Several outcomes of one decision
Send one row per outcome, each row listing the same options.
rows = DataFrame([
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2028?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
{
"question": "How many sitting parliamentarians will be listed on ControlAI's campaign statement on December 31, 2030?",
"grant": ["$0 (no grant)", "$250k", "$1M"],
},
])
Outcomes of different types, a date and a number for instance, need separate calls today.
Someone else's decision
The decider can be a company, a regulator or a government.
result = await decision(
input=DataFrame([
{
"question": "By what percentage will weekday vehicle entries into the charging zone fall during the first full year of operation, compared with the year before?",
"charge": [
"Introduce no charge, leave the zone as it is",
"Charge $9 per entry on weekdays between 6am and 8pm",
"Charge $15 per entry on weekdays between 6am and 8pm",
"Charge $9 at peak hours only, free at other times",
],
},
]),
context=(
"The city is Chicago, and the zone is the central cordon bounded by Lake Michigan "
"on the east, Chicago Avenue on the north, Halsted Street on the west and "
"Roosevelt Road on the south, which about 360,000 vehicles enter on an average "
"weekday. Chicago Transit Authority buses and trains carried 319.2 million rides "
"in 2025 and Metra commuter rail carried 38.1 million, and all eleven Metra lines "
"end at four downtown terminals inside or on the edge of that cordon."
),
alternatives_field="charge",
forecast_type="numeric",
output_field="entry_reduction",
units="percent",
)
Weekday entries fall 0 percent under no charge, 12.5 under $9, 17.8 under $15 and 7.0 under $9 at peak only: the published run. For a third party, context carries the terms of the decision, when it is taken and what stays fixed, rather than facts about them. If nobody decides the premise, use a conditional forecast instead.
When the options come back level
Cutting, holding or raising rates at the Fed's next meeting gives unemployment in December 2028 of 4.29, 4.27 and 4.31 percent (the run). The ten-year yield a month after the meeting comes back 5.11, 4.95 and 4.94 percent (the run). A level result is an answer, and the same decision can matter for a nearer outcome.
When something looks wrong
Wrong size. The forecaster did not know your scale and picked a typical one. Add two sentences to context: who you are and how big.
Ranges overlap. Read the rationale for what dominates instead, then ask for a nearer or narrower outcome if you need numbers that separate. Boeing's 2029 earnings per share came back at $7.10 if the board keeps its chief executive and $5.90 if it replaces them, with both ranges running from about $0 to $14 (the run).
A date comes back as "never". Asked in which year Boeing next outdelivers Airbus if it commits to a clean-sheet jet, the medians landed in the 2040s and the p90 was "never" under both options. Read that as: this may not happen under either option, so ask for a milestone that will arrive, or a quantity at a date.
The forecast disputes a fact you gave. It checks what you pass, so check your dates and names. An invented organization whose name is real will pull in the real one's facts.
The forecast is about the world, not about you. "What will energy prices be?" is the same under every option; "what will we spend?" is not. Point the outcome at whoever is deciding.
The options do not compare. They were run in separate calls, or one of them belongs to a different decider. Put every option in one call, and keep to one decider and one act.
It ran a conditional forecast. In the app, say "we're deciding" and list the options; in code, use decision() with alternatives_field, not forecast() with condition.
Two runs disagree. Runs vary by a few percent on a number and a few points on a probability, so repeat the run before trusting a gap that small.
Related
- Forecasting a decision: parameters, outcome shapes and intervention assumptions.
- Case studies: grant funding at three levels and Which CEO Replacement Maximizes Share Price.
- How We Can Prevent Rogue Agents: four decisions by OpenAI, Anthropic and the US government.