Targets describe an intended outcome. Forecasts estimate what is likely under stated assumptions. Actuals describe what happened. Combining them on one dashboard is useful only when their grains, versions, and time boundaries are compatible.
GraphJSON does not currently provide a native goal-setting system, planning workflow, forecasting engine, or scheduled model runner. Keep approved plans in the planning authority, calculate forecasts in a governed service or notebook, and publish bounded versioned results to GraphJSON.
Distinguish the three series#
| Series | Question | Authority |
|---|---|---|
| Actual | What happened? | Governed metric implementation |
| Target | What did we commit or aspire to achieve? | Planning process |
| Forecast | What do we currently expect under assumptions? | Forecast model |
Do not rename a target as a forecast to make it appear evidence-based, or rewrite a prior target after the period closes.
Define one compatible grain#
State:
metric key
entity or population
time grain
time zone
period start and end
aggregation
currency or unit
segment
definition version
A monthly account target cannot be compared directly with a rolling 30-day user forecast. Align the definition before charting.
Publish targets as immutable versions#
{
"event": "metric_target_published",
"target_id": "weekly_active_accounts:2026_w31:operating_plan:v2",
"metric_key": "weekly_active_accounts",
"metric_definition_version": 4,
"period_start": "2026-07-27T00:00:00Z",
"period_end": "2026-08-03T00:00:00Z",
"time_zone": "UTC",
"segment": "all_eligible_accounts",
"target_value": 1400,
"target_type": "operating_plan",
"plan_version": 2,
"approved_at": "2026-07-20T18:00:00Z"
}
If the plan changes, publish a new version with an effective date and reason. Keep the original so a retrospective can compare results with the plan known at the time.
Separate:
- committed target
- aspiration or stretch
- minimum guardrail
- capacity threshold
- contractual objective
These have different decision consequences.
Publish forecast snapshots#
One forecast is a vintage: the estimate as of one cutoff.
{
"event": "metric_forecast_published",
"forecast_id": "weekly_active_accounts:2026_w31:2026-07-20:model_3",
"metric_key": "weekly_active_accounts",
"metric_definition_version": 4,
"period_start": "2026-07-27T00:00:00Z",
"period_end": "2026-08-03T00:00:00Z",
"forecast_as_of": "2026-07-20T12:00:00Z",
"point_estimate": 1335,
"lower_bound": 1240,
"upper_bound": 1430,
"interval_level": 0.8,
"model_key": "seasonal_account_growth",
"model_version": 3,
"scenario": "base",
"data_complete_through": "2026-07-19T23:00:00Z"
}
Do not overwrite last week’s forecast with today’s estimate. Forecast vintages are necessary to measure whether the model was useful at the decision time.
Preserve assumptions and scenarios#
Keep a bounded scenario code:
- base
- upside
- downside
- committed_capacity
Document assumptions in the model repository or planning system:
- acquisition spend
- release timing
- price changes
- seasonality
- capacity
- churn or conversion rates
- known customer contracts
Do not put confidential planning narratives in GraphJSON. Store the scenario code, model version, and approved summary.
Select the correct target and forecast#
For current operating reporting, choose the latest approved version known before the period:
WITH targets AS (
SELECT
JSONExtractString(json, 'metric_key') AS metric_key,
parseDateTimeBestEffort(JSONExtractString(json, 'period_start')) AS period_start,
parseDateTimeBestEffort(JSONExtractString(json, 'period_end')) AS period_end,
argMax(
JSONExtractFloat(json, 'target_value'),
tuple(
JSONExtractInt(json, 'plan_version'),
toDateTime(timestamp)
)
) AS target_value,
max(JSONExtractInt(json, 'plan_version')) AS plan_version
FROM planning_events
WHERE
JSONExtractString(json, 'event') = 'metric_target_published'
AND JSONExtractString(json, 'target_type') = 'operating_plan'
GROUP BY metric_key, period_start, period_end
)
SELECT *
FROM targets
ORDER BY period_start
For a retrospective, select the version that was approved at the historical decision cutoff rather than today’s latest revision.
Calculate actuals independently#
The actual series should use the canonical governed metric, not a simplified planning approximation. Preserve:
- definition version
- source completeness
- occurrence time
- exclusions
- restatement status
If the actual definition changes, decide whether to restate targets and forecasts or preserve the historical comparison. Do not compare unlike definitions without a visible break.
Calculate variance deliberately#
absolute variance = actual − target
percentage variance = (actual − target) / abs(target)
attainment = actual / target
Percentage variance is undefined or misleading near zero. For costs, errors, and latency, a lower actual may be favorable; for conversion or availability, higher may be favorable.
Store or derive the metric’s direction:
higher_is_better
lower_is_better
within_range
Do not use green and red coloring without direction and accessibility-safe labels.
Represent uncertainty honestly#
A forecast interval is not a guaranteed range. Document:
- interval level
- statistical or scenario method
- data cutoff
- model version
- treatment of one-off events
- known structural breaks
Show the interval with the point estimate. Avoid displaying false precision
such as 1,334.72 accounts.
GraphJSON can chart published bounds but does not calculate or certify them.
Backtest forecast vintages#
After actuals settle, compare each historical forecast with the actual:
forecast error = actual − point estimate
absolute error = abs(forecast error)
interval covered = lower bound <= actual <= upper bound
Evaluate by horizon, season, segment, and model version. A one-week forecast and a six-month forecast should not share one accuracy claim.
Use several measures:
- mean absolute error
- median absolute error
- bias
- interval coverage
- directional accuracy where relevant
MAPE is unstable when actuals are zero or near zero. Choose an accuracy measure appropriate to the metric.
Handle missing and partial actuals#
Before period close, label actuals:
- partial
- provisional
- final
- restated
Do not compare a partial month with a full-month target without an explicit pace calculation. Linear pacing is inappropriate when the metric is seasonal within the period.
Use source watermarks from Derived events and scheduled rollups.
Build the planning dashboard#
Show:
- actual, approved target, and current forecast
- forecast interval
- plan and model versions
- absolute variance and direction-aware attainment
- partial or final status
- prior forecast vintages
- backtest accuracy by horizon
- scenario comparison
- source and model freshness
Use Time-series baselines to answer whether actual behavior is unusual. A target miss and an anomaly are different: a stable metric can consistently miss an unrealistic target.
Review checklist#
- Actual, target, and forecast remain distinct series.
- All three use compatible metric, grain, period, and unit definitions.
- Targets preserve approved versions and effective dates.
- Forecasts preserve vintage, model, scenario, and data cutoff.
- Actuals come from the canonical governed metric.
- Variance direction is explicit.
- Partial periods are not compared as complete.
- Forecast intervals show level and method.
- Backtests use historical vintages, not overwritten forecasts.
- Model scheduling and planning authority remain outside GraphJSON.