{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Lesson 21: Decision Optimization, Inventory, and Business Impact\n",
        "\n",
        "**CS230 inspiration:** Project strategy, model evaluation, and translating predictions into decisions.\n",
        "\n",
        "**Demand forecasting focus:** Convert demand forecasts into inventory decisions with asymmetric costs.\n",
        "\n",
        "**Learning objectives**\n",
        "\n",
        "- Evaluate forecasts through the business decision they support.\n",
        "- Use quantile logic for service-level inventory decisions.\n",
        "- Compare forecast accuracy improvements with operational cost improvements.\n",
        "\n",
        "**Senior-readiness checkpoint:** You can justify a model by profit, service level, and risk rather than metric vanity.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Code Cell 1 Explanation\n",
        "\n",
        "**What this cell does:** This setup cell imports the shared scientific Python stack and the curriculum helpers used by later cells. It also establishes the deterministic seed convention so repeated runs are comparable.\n",
        "\n",
        "**Why it matters:** A restart-and-run notebook should make its dependencies explicit before any modeling work begins. For forecasting, reproducibility is especially important because small data or seed changes can change validation conclusions.\n",
        "\n",
        "**Key operations to notice:** confirm that shared helper imports match the lesson's code path; check that `SEED` is defined before any generated data or model training\n",
        "\n",
        "**What to inspect:** Check that imports are grouped by purpose: general analysis packages, forecasting data helpers, metrics, plotting utilities, and any lesson-specific libraries.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "\n",
        "from demand_forecasting_curriculum.data import (\n",
        "    DEFAULT_FEATURE_COLUMNS,\n",
        "    generate_daily_demand,\n",
        "    make_sequence_dataset,\n",
        "    make_supervised_table,\n",
        "    time_series_split,\n",
        ")\n",
        "from demand_forecasting_curriculum.metrics import (\n",
        "    interval_coverage,\n",
        "    mase,\n",
        "    pinball_loss,\n",
        "    smape,\n",
        "    wmape,\n",
        ")\n",
        "from demand_forecasting_curriculum.plotting import plot_predictions, plot_series\n",
        "\n",
        "SEED = 42\n",
        "np.random.seed(SEED)\n",
        "\n",
        "from sklearn.ensemble import HistGradientBoostingRegressor\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Hiring-Manager Signal\n",
        "\n",
        "The best Data Scientists do not stop at WMAPE. They understand the downstream decision, the cost of being wrong in each direction, and how to choose a forecast or quantile that improves the business outcome.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Code Cell 2 Explanation\n",
        "\n",
        "**What this cell does:** This cell advances **Decision Optimization, Inventory, and Business Impact** by working on synthetic demand generation, time-based splitting, supervised feature construction, forecast metrics. It creates or updates `df`, `supervised`, `train`, `valid`, `test`, `feature_cols` and prepares evidence that later cells use rather than hiding state in prose.\n",
        "\n",
        "**Why it matters:** The goal is tied to this lesson's forecasting focus: Convert demand forecasts into inventory decisions with asymmetric costs. Each object is kept inspectable so the learner can connect code, assumptions, and modeling consequences.\n",
        "\n",
        "**Key operations to notice:** creates a synthetic retail panel with known demand drivers; turns time series rows into forecast-origin training examples; keeps train, validation, and test windows ordered by date; trains a strong tabular baseline; evaluates scale-weighted forecast error\n",
        "\n",
        "**What to inspect:** Before moving on, inspect shapes, date ranges, metric values, segment behavior, or generated tables. If a value looks surprisingly good, surprisingly bad, or unavailable at forecast time, treat that as a modeling clue.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "df = generate_daily_demand(n_stores=4, n_items=8, periods=560, seed=SEED)\n",
        "supervised = make_supervised_table(df, horizon=7)\n",
        "train, valid, test = time_series_split(supervised, valid_days=70, test_days=70)\n",
        "feature_cols = [\"store_id\", \"item_id\", *DEFAULT_FEATURE_COLUMNS]\n",
        "\n",
        "model = HistGradientBoostingRegressor(max_iter=120, learning_rate=0.04, random_state=SEED)\n",
        "model.fit(train[feature_cols], train[\"y\"])\n",
        "\n",
        "valid = valid.assign(prediction=model.predict(valid[feature_cols]))\n",
        "test = test.assign(prediction=model.predict(test[feature_cols]))\n",
        "valid_residual = valid[\"y\"] - valid[\"prediction\"]\n",
        "wmape(test[\"y\"], test[\"prediction\"])\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Code Cell 3 Explanation\n",
        "\n",
        "**What this cell does:** This cell advances **Decision Optimization, Inventory, and Business Impact** by working on forecast metrics, decision optimization. It creates or updates `underage_cost`, `overage_cost`, `critical_fractile`, `residual_quantile`, `decisions`, `decision_scores` and prepares evidence that later cells use rather than hiding state in prose.\n",
        "\n",
        "**Why it matters:** The goal is tied to this lesson's forecasting focus: Convert demand forecasts into inventory decisions with asymmetric costs. Each object is kept inspectable so the learner can connect code, assumptions, and modeling consequences.\n",
        "\n",
        "**Key operations to notice:** evaluates scale-weighted forecast error; converts forecast error into business cost; creates an inspectable table for assumptions, metrics, or decisions\n",
        "\n",
        "**What to inspect:** Before moving on, inspect shapes, date ranges, metric values, segment behavior, or generated tables. If a value looks surprisingly good, surprisingly bad, or unavailable at forecast time, treat that as a modeling clue.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "underage_cost = 7.0\n",
        "overage_cost = 2.0\n",
        "critical_fractile = underage_cost / (underage_cost + overage_cost)\n",
        "residual_quantile = valid_residual.quantile(critical_fractile)\n",
        "\n",
        "decisions = test.assign(\n",
        "    order_median=lambda x: x[\"prediction\"].clip(lower=0),\n",
        "    order_newsvendor=lambda x: (x[\"prediction\"] + residual_quantile).clip(lower=0),\n",
        "    order_seasonal=lambda x: x[\"lag_7\"].clip(lower=0),\n",
        ")\n",
        "\n",
        "def inventory_cost(actual, ordered):\n",
        "    shortage = np.maximum(actual - ordered, 0)\n",
        "    overage = np.maximum(ordered - actual, 0)\n",
        "    return underage_cost * shortage + overage_cost * overage\n",
        "\n",
        "decision_scores = pd.DataFrame(\n",
        "    [\n",
        "        (\n",
        "            \"seasonal_lag_7\",\n",
        "            inventory_cost(decisions[\"y\"], decisions[\"order_seasonal\"]).mean(),\n",
        "            wmape(decisions[\"y\"], decisions[\"order_seasonal\"]),\n",
        "        ),\n",
        "        (\n",
        "            \"median_ml_forecast\",\n",
        "            inventory_cost(decisions[\"y\"], decisions[\"order_median\"]).mean(),\n",
        "            wmape(decisions[\"y\"], decisions[\"order_median\"]),\n",
        "        ),\n",
        "        (\n",
        "            \"newsvendor_ml_forecast\",\n",
        "            inventory_cost(decisions[\"y\"], decisions[\"order_newsvendor\"]).mean(),\n",
        "            wmape(decisions[\"y\"], decisions[\"order_newsvendor\"]),\n",
        "        ),\n",
        "    ],\n",
        "    columns=[\"policy\", \"mean_inventory_cost\", \"wmape\"],\n",
        ").sort_values(\"mean_inventory_cost\")\n",
        "decision_scores\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Code Cell 4 Explanation\n",
        "\n",
        "**What this cell does:** This cell advances **Decision Optimization, Inventory, and Business Impact** by working on visualization, decision optimization. It creates or updates `service_levels`, `frontier`, `frontier`, `ax` and prepares evidence that later cells use rather than hiding state in prose.\n",
        "\n",
        "**Why it matters:** The goal is tied to this lesson's forecasting focus: Convert demand forecasts into inventory decisions with asymmetric costs. Each object is kept inspectable so the learner can connect code, assumptions, and modeling consequences.\n",
        "\n",
        "**Key operations to notice:** converts forecast error into business cost; visualizes model behavior or data quality; creates an inspectable table for assumptions, metrics, or decisions\n",
        "\n",
        "**What to inspect:** Before moving on, inspect shapes, date ranges, metric values, segment behavior, or generated tables. If a value looks surprisingly good, surprisingly bad, or unavailable at forecast time, treat that as a modeling clue.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "service_levels = np.linspace(0.50, 0.95, 10)\n",
        "frontier = []\n",
        "for level in service_levels:\n",
        "    order = (test[\"prediction\"] + valid_residual.quantile(level)).clip(lower=0)\n",
        "    frontier.append(\n",
        "        {\n",
        "            \"service_level_target\": level,\n",
        "            \"actual_fill_rate\": float(np.mean(order >= test[\"y\"])),\n",
        "            \"mean_inventory_cost\": inventory_cost(test[\"y\"], order).mean(),\n",
        "            \"mean_units_ordered\": order.mean(),\n",
        "        }\n",
        "    )\n",
        "\n",
        "frontier = pd.DataFrame(frontier)\n",
        "ax = frontier.plot(\n",
        "    x=\"actual_fill_rate\",\n",
        "    y=\"mean_inventory_cost\",\n",
        "    marker=\"o\",\n",
        "    figsize=(7, 3),\n",
        "    legend=False,\n",
        ")\n",
        "ax.set_title(\"Inventory cost by achieved fill rate\")\n",
        "ax.set_xlabel(\"Achieved fill rate\")\n",
        "ax.set_ylabel(\"Mean inventory cost\")\n",
        "plt.show()\n",
        "frontier\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Lesson Recap\n",
        "\n",
        "In this lesson, we used 4 runnable code cells to work through **Decision Optimization, Inventory, and Business Impact**. The practical focus was: Convert demand forecasts into inventory decisions with asymmetric costs.\n",
        "\n",
        "We did this because deep learning for demand forecasting is not only about choosing an architecture. The learner needs to understand the data contract, the target and horizon, the validation path, the metric, and the operational decision supported by the forecast.\n",
        "\n",
        "The core takeaways were:\n",
        "\n",
        "- Evaluate forecasts through the business decision they support.\n",
        "- Use quantile logic for service-level inventory decisions.\n",
        "- Compare forecast accuracy improvements with operational cost improvements.\n",
        "\n",
        "By the end, you should be able to explain not just what the code produced, but why each step was necessary and what could go wrong if the same step were skipped or performed with leakage.\n",
        "\n",
        "**Senior-readiness reminder:** You can justify a model by profit, service level, and risk rather than metric vanity.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Exercises\n",
        "\n",
        "- Change underage and overage costs and explain how the chosen quantile moves.\n",
        "- Compare the model with lower WMAPE to the policy with lower inventory cost.\n",
        "- Write an executive recommendation for the inventory team in five sentences.\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python (deep-learning-demand)",
      "language": "python",
      "name": "deep-learning-demand"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
