Re-forecast Delta
Diffs a prior vs current re-forecast by driver and walks what moved as a waterfall, naming the biggest mover. The 'what changed since last month' chart.
Segment re-forecast · May vs Jun 2026
$2.09M
-$5.0k vs $2.09M prior
- Increase
- Decrease
- Balance
Gross profit re-forecast for May vs Jun 2026 fell from $2.09M to $2.09M vs prior — biggest move SMB (+$110k).
| Driver | Prior | Current | Change |
|---|---|---|---|
| Enterprise | $1.28M | $1.28M | +$5.0k |
| Mid-market | $620k | $575k | -$45k |
| SMB | $210k | $320k | +$110k |
| Churn | -$78k | -$158k | -$80k |
| Services | $60k | $65k | +$5.0k |
| Total | $2.09M | $2.09M | -$5.0k |
FP&A consolidation · updated daily at 07:00 UTC
Demo data is illustrative. Replace with your own typed data prop.
How it computes
Driver-level waterfall of the change between a prior and a current forecast.
from = Σ prior; stepᵢ.delta = currentᵢ − priorᵢ; to = Σ currentAssumptions
- Drivers are matched by position, not by name — the caller keeps the arrays aligned.
- Each driver moves independently; no interaction is modeled.
- The bridge starts from the sum of the prior driver values.
Honest about
- Ragged inputs truncate to the shortest array silently — extra drivers are dropped without warning.
- These are point deltas: there's no test of whether a swing is statistically meaningful.
- If the drivers are a decomposition, their sum may not equal a reported starting balance — no reconciliation is done.
When to reach for it
The hardest question in a monthly re-forecast is what changed since last month, and why? Use the Re-forecast Delta when you have two snapshots of the same metric — last month's view vs this month's, or plan vs current trajectory — and need to show, by driver, exactly what moved the total. It's the bridge a CFO asks for when the number slipped and nobody can say where.
How it thinks
Give it one row per driver with a prior and a current value. The widget:
- Computes the per-driver change with
diffSeries()(current − prior). - Walks those deltas from the prior total to the current total with
buildBridge(), rendering the standard waterfall — gains up, losses down, anchors for the start/end balances. - Surfaces the biggest mover via a
reforecastDeltainsight: "Gross profit re-forecast for Q3 FY26 fell from $1530k to $1455k vs prior — biggest move Churn (-$60k)."
It reuses the same shared WaterfallPlot as the P&L bridge, so the visual language is consistent across the pack.
Good to know
- The labels are arbitrary — drivers, segments, line items, or periods — as long as
priorandcurrentalign row-for-row. - When the net move is small relative to the totals, the step bars are thin by design (a waterfall is honest about scale); the narrative and the data table carry the exact figures.
- A bad shape degrades to the error state via
safeData; an empty input shows the empty state rather than a misleading "$0 → $0" walk.
Use it with your data
Data contract
Passed via the data prop — an array of objects: validated at runtime, so a bad shape degrades to the error state rather than crashing.
| Field | Type | Required |
|---|---|---|
| label | string | yes |
| prior | number | yes |
| current | number | yes |
Static props
import { FpaReforecastDelta } from "@/components/blocks/fpa-reforecast-delta/fpa-reforecast-delta";
<FpaReforecastDelta data={myData} />Client fetch (SWR)
const { data, isLoading, error } = useSWR("/api/metric", fetcher);
// route isLoading/error through <ChartStates>, then:
<FpaReforecastDelta data={data} />Server component (RSC)
const data = await fetch(url, {
next: { revalidate: 3600 },
}).then((r) => r.json());
<FpaReforecastDelta data={data} />Map arbitrary rows
import { mapRows } from "@/lib/sextant/column-map";
const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <FpaReforecastDelta data={out.data} />;Make it yours
Every customization below is an optional prop — omit them all and you get the defaults shown above. Recolor, reformat, restyle, or drop the card chrome without forking the component.
Recolor or rename a series (gain, loss, anchor)
<FpaReforecastDelta
series={{ gain: { color: "var(--chart-3)", label: "…" } }}
/>Format numbers (currency / locale)
const eur = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
notation: "compact",
});
<FpaReforecastDelta valueFormatter={(n) => eur.format(n)} />Replace or hide the narrative
<FpaReforecastDelta narrative="Your own one-liner." />
// …or hide it entirely:
<FpaReforecastDelta narrative={false} />Restyle any region
<FpaReforecastDelta
className="max-w-xl"
classNames={{ body: "bg-muted/20" }}
/>