← Files Power BI DesktopARCHIVED FILE

scripts/support/forecast_backtest.py

1.8 KB · Oct 2, 2026 · 00:32 UTC

↓ Download file

from forecast_engine import calculate_forecast
from forecast_primitives import MONTH_NAMES, month_label, num
from forecast_summary import build_summary_rows


BACKTEST_FIELDNAMES = [
    "as_of_month", "forecast_month", "horizon_months", "actual_sales",
    "ai_forecast", "roll_forecast", "absolute_error", "roll_absolute_error", "bias",
]


def build_backtest_rows(rows, forecast_year, start_month, horizon_months, grain):
    detail_rows = []
    for as_of_month in range(1, max(1, start_month)):
        detail_rows.extend(
            backtest_month(rows, forecast_year, as_of_month, horizon_months, grain)
        )
    return detail_rows


def backtest_month(rows, forecast_year, as_of_month, horizon_months, grain):
    horizon_end = min(12, as_of_month + horizon_months)
    simulated = calculate_forecast(
        rows,
        forecast_year,
        as_of_month + 1,
        horizon_end,
        month_label(forecast_year, as_of_month),
        horizon_months,
        grain,
    )
    return [backtest_row(row, as_of_month) for row in build_summary_rows(simulated)]


def backtest_row(row, as_of_month):
    actual = num(row["actual_sales"])
    ai_forecast = num(row["final_ai_forecast"])
    roll = num(row["roll_forecast"])
    return {
        "as_of_month": row["forecast_month"],
        "forecast_month": row["forecast_month"],
        "horizon_months": month_number(row["forecast_month"]) - as_of_month,
        "actual_sales": round(actual, 2),
        "ai_forecast": round(ai_forecast, 2),
        "roll_forecast": round(roll, 2),
        "absolute_error": round(abs(ai_forecast - actual), 2),
        "roll_absolute_error": round(abs(roll - actual), 2),
        "bias": round(ai_forecast - actual, 2),
    }


def month_number(label):
    return next(number for number, name in MONTH_NAMES.items() if label.startswith(name))

SHA-256: edaa74dca45cf199edb0d2d15e9a491537e1c42d4a5a2b69ba245a71043215a8