Forecasting demo walkthrough 🚀

“Goal: build a compelling demo using only composable pieces. As context evolved, new micro-tools appeared.”

f

What we’ll try

We’ll create synthetic time series with:

For each, we’ll produce forecasts for the next month, quarter, and year, then plot actuals vs predictions

Algorithms in plain language

Here’s what each method tries to do, in simple terms:

Mini-map: how context shaped the tools

  1. Baseline – Needed clean daily data → generate_series.py
  2. Spikes appear – Added add_spikes.py to simulate noisy launches
  3. Seasonality shows up – Built add_seasonality.py
  4. Forecast tuning – Tweaked parameters for specific business patterns

Each step uses the same pipe-first workflow so you can remix or extend it.

0) Setup (one time per shell)

source .venv/bin/activate
pip install -r requirements.txt
mkdir -p demo/input demo/out

1) Flat (no noise) — one‑year of daily data

First, a perfectly flat year with no noise.

python demo/generate_series.py \
  --pattern flat --granularity daily --periods 365 \
  --baseline 10 --noise 0.0 \
  --out demo/input/daily_flat.csv

Flat

Now I run the forecaster and include an ensemble column which averages available models.

python forecast.py \
  --input demo/input/daily_flat.csv \
  --date-column PeriodStart --value-column Cost \
  --ensemble > demo/out/daily_flat_forecasts.csv

The output CSV contains the original values and one column per forecasting method.

Forecasted data

Good news, every forecasting method manage flat well.

Growing a little each day

Second, a predictable linear growth pattern—half a unit per day.

python demo/generate_series.py \
  --pattern upward_trend --granularity daily --periods 365 \
  --baseline 10 --trend 0.5 --noise 0.0 \
  --out demo/input/daily_growth.csv

The trend increases the baseline steadily; no noise keeps it clean for comparison.

Growing slowly

Now I run the forecaster and include an ensemble column which averages available models.

python forecast.py \
  --input demo/input/daily_growth.csv \
  --date-column PeriodStart --value-column Cost \
  --ensemble > demo/out/daily_growth_forecasts.csv

The output CSV contains the original values and one column per forecasting method.

Forecasted data

3) Flat with spikes (≤10% daily)

Why it matters: Shows how small, infrequent spikes nudge forecasts and why ensemble smoothing helps avoid overreacting.

Third, I introduce occasional positive spikes up to ten percent on a flat baseline.

python demo/generate_series.py \
  --pattern flat --granularity daily --periods 365 \
  --baseline 10 --noise 0.0 \
  --out demo/input/daily_flat_10_base.csv
python demo/add_spikes.py \
  --input demo/input/daily_flat_10_base.csv \
  --output demo/input/daily_flat_spikes_10.csv \
  --max-pct 0.10 --prob 0.05

This applies bounded spikes (max ten percent) with a small daily probability, using a fixed seed for reproducibility.

Growing slowly

I'll run forecasts and show how each model handles transient spikes over the three horizons.

python forecast.py \
  --input demo/input/daily_flat_spikes_10.csv \
  --date-column PeriodStart --value-column Cost \
  --ensemble > demo/out/daily_flat_spikes_10_forecasts.csv

Forecasted data

4) Flat with larger spikes (≤33% daily)

Why it matters: Bigger spikes with higher probability stress models—useful for promo-heavy or launch-driven workloads.

Larger spikes up to 33 percent on a flat baseline, stressing the models more.

python demo/generate_series.py \
  --pattern flat --granularity daily --periods 365 \
  --baseline 10 --noise 0.0 \
  --out demo/input/daily_flat_33_base.csv
python demo/add_spikes.py \
  --input demo/input/daily_flat_33_base.csv \
  --output demo/input/daily_flat_spikes_33.csv \
  --max-pct 0.33 --prob 0.15

This applies bounded spikes (max 33 percent) with higher daily probability, using a fixed seed for reproducibility.

Flat with larger spikes

I'll run forecasts and show how each model handles transient spikes over the three horizons.

python forecast.py \
  --input demo/input/daily_flat_spikes_33.csv \
  --date-column PeriodStart --value-column Cost \
  --ensemble > demo/out/daily_flat_spikes_33_forecasts.csv

Forecasted data

5) Growth with spikes (≤33% daily) 15% change of happening

Why it matters: Real FinOps pipelines often mix trend + events; this section shows interplay of growth and burstiness.

Finally, growth with larger spikes up to twenty percent, stressing the models.

python demo/generate_series.py \
  --pattern upward_trend --granularity daily --periods 365 \
  --baseline 100 --trend 0.5 --noise 0.0 \
  --out demo/input/daily_growth_33_base.csv
python demo/add_spikes.py \
  --input demo/input/daily_growth_33_base.csv \
  --output demo/input/daily_growth_spikes_33.csv \
  --max-pct 0.33 --prob 0.15

Growing slowly

I'll run forecasts and show how each model handles transient spikes over the three horizons.

python forecast.py \
  --input demo/input/daily_growth_spikes_33.csv \
  --date-column PeriodStart --value-column Cost \
  --ensemble > demo/out/daily_growth_spikes_33_forecasts.csv

Forecasted data

6) Daily seasonality — flat baseline (toys and holidays)

Why it matters: Retail-style cyclicality; illustrates how external adapters layer in context before forecasting.

Flat baseline (no noise) with two seasonal profiles applied externally, then forecasted.

python demo/generate_series.py --pattern flat --granularity daily --periods 365 --baseline 100 --noise 0.0 --out demo/input/daily_flat_base.csv && python demo/add_seasonality.py --input demo/input/daily_flat_base.csv --output demo/input/daily_flat_toys.csv --preset toys && python forecast.py --input demo/input/daily_flat_toys.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_flat_toys_forecasts.csv && python demo/add_seasonality.py --input demo/input/daily_flat_base.csv --output demo/input/daily_flat_holidays.csv --preset holidays && python forecast.py --input demo/input/daily_flat_holidays.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_flat_holidays_forecasts.csv

Flat + Toys seasonality

Forecasted data (Toys)

Flat + Holidays seasonality

Forecasted data (Holidays)

7) Daily seasonality — growth baseline (toys and holidays)

Why it matters: Growth + seasonality is a classic FinOps forecasting headache; compare outputs before tuning.

Upward trend baseline with the same two seasonal profiles.

python demo/generate_series.py --pattern upward_trend --granularity daily --periods 365 --baseline 100 --trend 0.5 --noise 0.0 --out demo/input/daily_growth_base.csv && python demo/add_seasonality.py --input demo/input/daily_growth_base.csv --output demo/input/daily_growth_toys.csv --preset toys && python forecast.py --input demo/input/daily_growth_toys.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_growth_toys_forecasts.csv && python demo/add_seasonality.py --input demo/input/daily_growth_base.csv --output demo/input/daily_growth_holidays.csv --preset holidays && python forecast.py --input demo/input/daily_growth_holidays.csv --date-column PeriodStart --value-column Cost --ensemble > demo/out/daily_growth_holidays_forecasts.csv

Growth + Toys seasonality

Forecasted data (Toys)

Growth + Holidays seasonality

Forecasted data (Holidays)

Optional: tuning when seasonality is known

If you know your industry has yearly seasonality (e.g., toys or holidays), the defaults already work reasonably well for daily data. Prophet has yearly seasonality enabled by default and tends to capture these patterns without extra tuning. If you want gentle tweaks:

# Daily data with yearly seasonality: nudge Prophet priors and keep ensemble
python forecast.py \
  --input demo/input/daily_flat_toys.csv \
  --date-column PeriodStart --value-column Cost \
  --prophet-yearly-seasonality true \
  --prophet-weekly-seasonality false \
  --prophet-changepoint-prior-scale 0.05 \
  --prophet-seasonality-prior-scale 12.0 \
  --ensemble > demo/out/daily_flat_toys_forecasts_tuned.csv

Notes:

A couple of alternative tuning examples

# Slightly more responsive moving average and ES
python forecast.py \
  --input demo/input/daily_growth_holidays.csv \
  --date-column PeriodStart --value-column Cost \
  --sma-window 14 \
  --es-alpha 0.7 \
  --ensemble > demo/out/daily_growth_holidays_forecasts_tuned_baselines.csv

# Add weekly effects (only if real weekly pattern exists) and SARIMA weekly seasonality
python forecast.py \
  --input demo/input/daily_growth_holidays.csv \
  --date-column PeriodStart --value-column Cost \
  --prophet-weekly-seasonality true \
  --sarima-order 1,1,1 \
  --sarima-seasonal-order 1,0,1,7 \
  --ensemble > demo/out/daily_growth_holidays_forecasts_with_weekly.csv

8) Tuned seasonal forecasts (examples)

Why it matters: Demonstrates parameter tuning (Prophet priors, SARIMA weekly) once you know the business rhythm.

Two tuned runs to illustrate parameter effects when yearly seasonality is known.

# Flat + toys seasonality (slightly stronger seasonality prior)
python forecast.py \
  --input demo/input/daily_flat_toys.csv \
  --date-column PeriodStart --value-column Cost \
  --prophet-yearly-seasonality true \
  --prophet-weekly-seasonality false \
  --prophet-changepoint-prior-scale 0.05 \
  --prophet-seasonality-prior-scale 12.0 \
  --ensemble > demo/out/daily_flat_toys_forecasts_tuned.csv

# Growth + holidays seasonality (enable weekly; add SARIMA weekly component)
python forecast.py \
  --input demo/input/daily_growth_holidays.csv \
  --date-column PeriodStart --value-column Cost \
  --prophet-weekly-seasonality true \
  --sarima-order 1,1,1 \
  --sarima-seasonal-order 1,0,1,7 \
  --ensemble > demo/out/daily_growth_holidays_forecasts_tuned.csv

Tuned forecast: Flat + Toys

Tuned forecast: Growth + Holidays

Apendix