Forecast
Where a metric is heading, with honest uncertainty, scenarios, and when it reaches a goal or crosses a limit.
≈ 2,309 tokens per use (tool 1,517 · guidance 340 · typical payload 452)How token costs are estimated
Example: Forecast
Forecast next quarter's revenue.
Revenue forecast, Q4 2026
Monthly revenue
$1.18Min Dec 2026 (median forecast)
Likely between $1.05M and $1.31M
+12% vs Sep 2026. The median doesn't reach Goal $1.2M by Dec 2026. The high end of the 80% range reaches it around Nov 2026.
- Actual
- Median forecast
- 50% range
- 80% range
Monthly revenue forecast: $1.18M in Dec 2026 (median), likely between $1.05M and $1.31M. That's +12% from the last actual, $1.05M in Sep 2026. Use the left and right arrow keys to move between points.
| Period | Actual | Median forecast | 80% range | 50% range |
|---|---|---|---|---|
| Apr 2025 | $780,000 | — | — | — |
| May 2025 | $800,000 | — | — | — |
| Jun 2025 | $790,000 | — | — | — |
| Jul 2025 | $760,000 | — | — | — |
| Aug 2025 | $780,000 | — | — | — |
| Sep 2025 | $840,000 | — | — | — |
| Oct 2025 | $860,000 | — | — | — |
| Nov 2025 | $980,000 | — | — | — |
| Dec 2025 | $1,060,000 | — | — | — |
| Jan 2026 | $840,000 | — | — | — |
| Feb 2026 | $820,000 | — | — | — |
| Mar 2026 | $900,000 | — | — | — |
| Apr 2026 | $930,000 | — | — | — |
| May 2026 | $950,000 | — | — | — |
| Jun 2026 | $940,000 | — | — | — |
| Jul 2026 | $910,000 | — | — | — |
| Aug 2026 | $960,000 | — | — | — |
| Sep 2026 | $1,050,000 | — | — | — |
| Oct 2026 | — | $1,000,000 | $930,000 to $1,070,000 | $965,000 to $1,035,000 |
| Nov 2026 | — | $1,100,000 | $1,010,000 to $1,190,000 | $1,060,000 to $1,145,000 |
| Dec 2026 | — | $1,180,000 | $1,050,000 to $1,310,000 | $1,115,000 to $1,245,000 |
Drivers and assumptions
- Pushes up: Holiday season · Dec ran 26% above Sep last year
- Pushes up: Wholesale account with Fabrikam · Ships from November
- Pushes down: Paid search budget cut · Down 15% from October
Seasonal model on 18 months of order data · Last quarter's forecast was within 4% (MAPE 3.8%) Assumes no price changes.
When to use
- The user asks where a business or personal metric is heading: next quarter's revenue, when the runway ends, how many support tickets to expect, whether a savings goal will be met.
- A forecasting tool returned the history and the model's median, ideally with quantile bands.
When not to use
- You have no forecasting tool result. Never extrapolate a trend yourself or invent bands.
- The user wants to move the inputs and watch the outcome change. Use Simulation.
- It's a weather forecast. Use Weather.
- The user only asks about the past. Use a Chart.
How the agent should use it
- Copy
history,forecast,scenarios,drivers,methodandaccuracyfrom the tool result unchanged. They're all data-bound. - Pass only the quantiles the model returned.
p10/p90draw the 80% range andp25/p75the 50% range. Without them the chart shows the median alone, which is better than a made-up band. - Use
thresholdfor a limit the user fears crossing (cash at 0, capacity) andtargetfor a goal. The component works out the crossing date itself, from the median and from the band edges, so don't put dates in the labels. - Keep
assumptionsNoteto one sentence. When the assumptions matter to the answer, follow with the Assumptions component so the user can correct them. - On
adjust, show Assumptions or Preference Controls; onalert, confirm the alert before creating it. Re-run the forecast after any change and supersede the old card. - Lifecycle: forecasts age. Set
fetchedAtto the time the model ran andstaleAfterMsto one period of the data (a day for daily data, a week for weekly), so the Renderer offers a refresh. When the forecast is re-run, mark the old call superseded. NoexpiresAt.
Reading the forecast
- Headline. The median at the end of the horizon in large type ("$1.18M in Dec 2026") with the 80% range in words under it. When the median crosses a
threshold, the crossing date leads instead ("~Aug 2027 Runway ends"). When the median reaches atarget, a line names it ("Goal $1.2M ~Dec 2026"). The change against the last actual is read to screen readers; when the median ends on the other side of zero, it's an amount, not a percentage. - Chart. Actuals are a solid line and the median a dashed line that continues from the last actual. The 80% range is the lighter band and the 50% range the darker one, both one chart color at low opacity. A vertical line marks today. One compact key row names the lines and bands.
- Crossings. Target and threshold lines are labeled. Where the median crosses one, a marker shows the date, found by linear interpolation between points (lines are drawn linearly too, so the marker sits on the line). The early and late dates from the 80% band edges, or the note that the median never gets there within the horizon, are read to screen readers.
- Scenarios. With
scenarios, a Median / Scenarios switch replaces the median and bands with up to three scenario lines, told apart by dash pattern as well as shade. Scenario notes are read to screen readers. - Drivers and assumptions.
drivers,method,accuracyandassumptionsNotesit behind a collapsed "Drivers and assumptions" disclosure, so the card leads with the answer.
Interaction
- Keyboard. The chart is focusable. Left and Right move between periods, starting at the last actual; Home and End jump to the ends and Escape clears. Each period's actual, or its median and ranges, is announced in a polite live region and shown in the tooltip.
- Actions.
actions: ["adjust"]adds Change assumptions;"alert"adds Set an alert for the threshold. Each press sends a result and leaves a short note in the footer. The forecast never locks: they're handoffs, and the user may press both.
Accessibility
The chart's SVG is hidden from screen readers. The focusable chart is described by a summary of the forecast, a visually hidden table lists every period with its actual, median, both ranges and each scenario, and the crossing sentences are visually hidden text. Driver arrows have "Pushes up" or "Pushes down" in hidden text, so direction isn't carried by the icon alone.
Installation
npx shadcn@latest add https://your-site.com/r/forecast.json
Usage
import { tool } from "ai"
import { forecastTool } from "@/components/genui/forecast.schema"
const tools = {
[forecastTool.name]: tool({
description: forecastTool.description,
inputSchema: forecastTool.inputSchema,
}),
}Without actions the forecast is display-only, so give the tool an execute that returns immediately. With them, the agent receives:
{ action: "adjust" }
{ action: "alert", value: 0 }Render it through the Renderer under show_forecast, or directly:
<Forecast
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) => {
if (part.state !== "output-available") {
addToolResult({ tool: "show_forecast", toolCallId: part.toolCallId, output: result })
} else {
sendMessage({ text: `Forecast action: ${JSON.stringify(result)}` })
}
}}
/>While streaming, the header shows placeholders, the chart appears once there's history and at least two forecast points, and the drivers arrive last. The headline and crossing dates wait for the finished forecast, so they never jump.
With LangGraph
Push it from a graph node with ui.push({ name: "show_forecast", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result, or an array when the user took both actions. The notes show in the footer and onResult isn't called. The buttons stay available, since actions are handoffs.
<Forecast {...part.input} result={part.state === "output-available" ? part.output : undefined} />Schema
| Prop | Type | Notes |
|---|---|---|
title | string | |
metric | { label, format, currency?, unit? } | format: currency, number or percent (in points) |
granularity | "day" | "week" | "month" | "quarter" | "year" | Drives date labels ("Mar ’27", "Q1 2027", "week of Dec 21") |
history | { t, value }[] | Data-bound. 1–500 actuals, ISO dates |
forecast | { t, p50, p10?, p25?, p75?, p90? }[] | Data-bound. 1–200 points after the last actual |
scenarios | { id, label, points: { t, value }[], note? }[]? | Data-bound. Up to 3. note is read to screen readers only |
target | { value, label, by? }? | Goal line; the reach date is computed |
threshold | { value, label, direction }? | Limit line; direction is below or above |
drivers | { label, effect, note? }[]? | Data-bound. Up to 5; effect is up or down |
method | string? | Data-bound. e.g. "Seasonal model on 24 months of data" |
accuracy | { label, mape? }? | Data-bound. How past forecasts held up |
assumptionsNote | string? | One sentence, up to 200 characters |
asOf | string | Data-bound. When the forecast ran |
source | { label, url? }? | Data-bound. Linked when the URL is http(s) |
actions | ("adjust" | "alert")[]? | Omit for display-only |
Host props: status (streaming or ready), onResult, result and className.