Demand Forecasting During Grand Promotion for Online Retailing (JD.com)
Skill-Promotion-Demand-Decomposition · 04-供应链
1. 解决的问题
核心思想:把任意一个 SKU 的历史销量分解为两个互相独立的信号——「基线需求」(正常销售节奏)和「促销 lift」(大促拉动的额外需求)——分别建模、分别备货,并专门处理大促结束后需求虚高的「Post-Promotion Elevation(PPE)」问题,避免系统在大促后 3 个月内持续过量备货。
2. 核心算法逻辑
核心思想:把任意一个 SKU 的历史销量分解为两个互相独立的信号——「基线需求」(正常销售节奏)和「促销 lift」(大促拉动的额外需求)——分别建模、分别备货,并专门处理大促结束后需求虚高的「PostPromotion Elevation(PPE)」问题,避免系统在大促后 3 个月内持续过量备货。
3. 业务应用场景
场景 A:Momcozy 618 大促备货拆解(5 月初下单,lead time 6 周)
- 业务问题:吸奶器 SKU 日常基线销量 80 件/天,历史 618 大促期间销量 350 件/天(lift = 4.4x)。但大促后 3 周销量仅 45 件/天(post-dip)。传统方法按大促均值备货导致大促后积压 6 周。 - 数据要求:过去 2 年日销量(含促销标志位)、大促日历(618 日期区间) - 预期产出: - 业务价值:避免过量备货 5,345 件,按 $35/件 × 20% 持有成本 = 节省约 $37,415 仓储资金占用
场景 B:黑五/Prime Day 四次大促的 lift 系数标定
4. 输入数据要求
请查看原始代码模板获取输入规格。
5. 输出结果
请查看原始代码模板获取输出规格。
6. 业务价值 / ROI
- ROI 预估:
- Momcozy 四次大促(618/双11/黑五/Prime Day),以 S12 Pro 主力 SKU 估算:
- 传统"3倍均值备货"年均过量备货约 8,000-12,000 件
- 促销分解法节省过量备货约 60%,按 $35 × 20% 持有成本 = 年节省约 $33,600-$50,400
- 同时降低大促后 3 个月的 FBA 长期仓储费约 $2,000-$4,000
- 实施难度:⭐⭐☆☆☆(2/5)— pandas + scipy,无需 GPU
7. 代码模板
代码块数量:6 · 路径:paper2skills-code/supply_chain/promotion_demand_decomposition
"""
Skill-Promotion-Demand-Decomposition
基于 SPADE (arXiv:2411.05852, NeurIPS 2024) +
Hewage et al. (Journal of Forecasting 2025) +
Chi et al. JD.com (SSRN:4777632, 2024)
母婴跨境电商大促需求分解与备货量计算
"""
import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Optional
@dataclass
class PromoPlan:
sku_id: str
promo_name: str
baseline_daily: float
lift_multiplier: float
promo_days: int
post_dip_ratio: float
post_dip_days: int
pre_dip_ratio: float
pre_dip_days: int
@property
def baseline_stock(self) -> float:
return self.baseline_daily * (self.pre_dip_days + self.promo_days + self.post_dip_days)
@property
def lift_stock(self) -> float:
return (self.lift_multiplier - 1) * self.baseline_daily * self.promo_days
@property
def post_dip_reduction(self) -> float:
return self.post_dip_ratio * self.baseline_daily * self.post_dip_days
@property
def optimal_stock(self) -> float:
return self.baseline_stock + self.lift_stock - self.post_dip_reduction
@property
def naive_stock(self) -> float:
return self.lift_multiplier * self.baseline_daily * (self.pre_dip_days + self.promo_days + self.post_dip_days)
@property
def saving_vs_naive(self) -> float:
return self.naive_stock - self.optimal_stock
# ── Base-Lift 分解(Hewage 2025 方法)──────────────────────
def decompose_baseline_lift(
sales: pd.Series,
promo_flags: pd.Series,
method: str = "rolling_median",
) -> tuple[pd.Series, pd.Series, pd.Series]:
"""
Base-Lift 分解:total = baseline + lift + post_dip
8. 论文来源
- 2411.05852