paper2skills Playbook

Demand Forecasting During Grand Promotion for Online Retailing (JD.com)

Skill-Promotion-Demand-Decomposition · 04-供应链

causalexperimentforecastingmulti_agent供应链与补货MAS与智能体工程WF-A 智能补货
实现难度⭐⭐☆☆☆
业务视角
适用角色供应链负责人 · 采购负责人 · CEO / 运营 VP
适用平台Amazon FBA · 海外仓 · 多国仓位(美/欧/日)
什么情况下用库存周转率低,资金压在海外仓出不来;SKU 断货紧急空运,物流成本吃掉毛利;多仓库存分布不均
成功是什么样的库存周转天数从 90 天降到 60 天,断货率 <3%,海外仓综合成本降低 15-25%
业务痛点
库存周转天数太长资金压死了断货了只能空运救急成本爆了多市场库存分配不均

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