← Files Power BI DesktopARCHIVED FILE

scripts/support/powerbi_ai_forecast.py

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

↓ Download file

import argparse
import csv
import json
from pathlib import Path

from forecast_backtest import BACKTEST_FIELDNAMES, build_backtest_rows
from forecast_engine import calculate_forecast
from forecast_primitives import conversion_probability, load_rows
from forecast_quality import QUALITY_FIELDNAMES, build_quality_rows
from forecast_summary import (
    SUMMARY_FIELDNAMES,
    build_summary_rows,
    build_top_delta_rows,
)


DETAIL_FIELDNAMES = [
    "as_of_date", "forecast_month", "grain", "customer", "product",
    "customer_hierarchy", "product_line", "month", "month_no", "actual_sales",
    "open_backlog", "backlog_conversion_probability", "expected_backlog_revenue",
    "budget", "roll_forecast", "statistical_demand_forecast",
    "residual_demand_forecast", "raw_ai_forecast", "final_ai_forecast",
    "forecast_low", "forecast_high", "confidence", "risk_flag", "explanation",
]


def write_csv(path, rows, fieldnames):
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", newline="", encoding="utf-8-sig") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames, delimiter=";")
        writer.writeheader()
        writer.writerows(rows)


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True)
    parser.add_argument("--output-directory", required=True)
    parser.add_argument("--as-of-date", default="")
    parser.add_argument("--forecast-year", type=int, default=2026)
    parser.add_argument("--start-month", type=int, default=5)
    parser.add_argument("--end-month", type=int, default=12)
    parser.add_argument("--horizon-months", type=int, default=3)
    parser.add_argument(
        "--grain",
        choices=["CustomerProduct", "HierarchyProductLine"],
        default="CustomerProduct",
    )
    parser.add_argument("--backtest", action="store_true")
    return parser.parse_args()


def build_paths(output_dir):
    return {
        "detail": output_dir / "ai-forecast-detail.csv",
        "summary": output_dir / "ai-forecast-summary.csv",
        "top_delta": output_dir / "ai-forecast-top-deltas.csv",
        "backtest": output_dir / "ai-forecast-backtest.csv",
        "quality": output_dir / "ai-forecast-model-quality.csv",
    }


def write_outputs(paths, rows, input_rows, args):
    backtest_rows = build_backtest_rows(
        input_rows,
        args.forecast_year,
        args.start_month,
        args.horizon_months,
        args.grain,
    )
    write_csv(paths["detail"], rows, DETAIL_FIELDNAMES)
    write_csv(paths["summary"], build_summary_rows(rows), SUMMARY_FIELDNAMES)
    write_csv(paths["top_delta"], build_top_delta_rows(rows), DETAIL_FIELDNAMES)
    write_csv(paths["backtest"], backtest_rows, BACKTEST_FIELDNAMES)
    write_csv(paths["quality"], build_quality_rows(backtest_rows), QUALITY_FIELDNAMES)


def summary_payload(paths, rows, args):
    return {
        "schema": "codex.powerbi.aiForecast.v1",
        "rowCount": len(rows),
        "detailPath": str(paths["detail"]),
        "summaryPath": str(paths["summary"]),
        "topDeltaPath": str(paths["top_delta"]),
        "backtestPath": str(paths["backtest"]),
        "modelQualityPath": str(paths["quality"]),
        "forecastYear": args.forecast_year,
        "startMonth": args.start_month,
        "endMonth": args.end_month,
        "asOfDate": args.as_of_date,
        "horizonMonths": args.horizon_months,
        "grain": args.grain,
    }


def main():
    args = parse_args()
    input_rows = load_rows(args.input)
    rows = calculate_forecast(
        input_rows,
        args.forecast_year,
        args.start_month,
        args.end_month,
        args.as_of_date,
        args.horizon_months,
        args.grain,
    )
    paths = build_paths(Path(args.output_directory))
    write_outputs(paths, rows, input_rows, args)
    print(json.dumps(summary_payload(paths, rows, args), indent=2))


if __name__ == "__main__":
    main()

SHA-256: f5c088a0bb7fc84b1b6fb9855c38f23435067def2e21a8290d753ac4a855f564