Forecast

Where a metric is heading, with honest uncertainty, scenarios, and when it reaches a goal or crosses a limit.

Pattern

≈ 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.

Monthly revenue: actuals and forecast by month
PeriodActualMedian forecast80% range50% 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.

Oct 8, 2026Northwind finance warehouse

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, method and accuracy from the tool result unchanged. They're all data-bound.
  • Pass only the quantiles the model returned. p10/p90 draw the 80% range and p25/p75 the 50% range. Without them the chart shows the median alone, which is better than a made-up band.
  • Use threshold for a limit the user fears crossing (cash at 0, capacity) and target for 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 assumptionsNote to 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; on alert, confirm the alert before creating it. Re-run the forecast after any change and supersede the old card.
  • Lifecycle: forecasts age. Set fetchedAt to the time the model ran and staleAfterMs to 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. No expiresAt.

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 a target, 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, accuracy and assumptionsNote sit 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

app/api/chat/route.ts
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

PropTypeNotes
titlestring
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
methodstring?Data-bound. e.g. "Seasonal model on 24 months of data"
accuracy{ label, mape? }?Data-bound. How past forecasts held up
assumptionsNotestring?One sentence, up to 200 characters
asOfstringData-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.

Source

On this page