Sextant
← Catalog
Block · finance

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.

State
Size
Mode

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

Segment re-forecast — Gross profit re-forecast for May vs Jun 2026, prior vs current by driver
DriverPriorCurrentChange
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 = Σ current

Assumptions

  • 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:

  1. Computes the per-driver change with diffSeries() (current − prior).
  2. 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.
  3. Surfaces the biggest mover via a reforecastDelta insight: "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 prior and current align 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.

FieldTypeRequired
labelstringyes
priornumberyes
currentnumberyes

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" }}
/>