大宗供应链企业销量预测与智能补货系统 (二)智能补货中的动态库存计算

115 阅读27分钟

目录

  1. 基于舆情驱动的补货方案架构
  2. 不同安全库存方法的对比
  3. DDMRP模型与B等商品的具体应用
  4. PuLP求解器实现

核心目标

准确的销量预测和智能的补货策略最终都服务于一个核心目标:合理的备货量,而备货量的计算,除了预测值之外,一个至关重要的组成部分是安全库存

在舆情风险驱动的需求波动环境中,传统的固定安全库存方法已经失效。本方案采用动态安全库存(DSS, Dynamic Safety Stock) 的方法,综合考虑以下因素:

核心公式:
最终备货量 = 预测销售量 + 动态安全库存
          = 预测销售量 + 风险调整系数 × 需求变异系数
          
其中风险调整系数包括:
├─ 舆情压力指数 (LDI/CRI/GRI/FRI)
├─ 商品特性 (易腐性/季节性)
├─ 供应链可靠性 (交期稳定性)
└─ 采购商等级 (信用风险)

关键名词解释

DDMRP涉及多个专业术语,理解这些概念对正确应用系统至关重要:

「净流位置」(Net Flow Position, NFP)

  • 通俗理解:"现在库里有多少货可以用"
  • 精确定义:当前库存 - 已承诺订单量(下游已下单但尚未收货)
  • 计算式:NFP = 现有库存 - 待履行订单量
  • 为什么重要:不能只看库存数字,因为订单已经"承诺"给客户了,不能再用
  • 举例:库里有100单位,但有60单位已经被下游订单承诺,实际可用只有40单位

「解耦提前期」(Decoupling Lead Time, DLT)

  • 通俗理解:"从下单到收货要等几天"
  • 精确定义:从向供应商下单,到物料到达本地仓库的总时间(包括采购、生产、运输、清关等)
  • 包含内容:采购确认(1天) + 生产制造(5天) + 运输配送(3天) + 接收入库(1天) = 10天
  • 为什么重要:这个周期越长,库存缓冲需求就越大
  • ABC-XYZ体现:不同供应商DLT不同(长期供应商短,新供应商长)

「可变性系数」(Variability Factor, VF)

  • 通俗理解:"需求有多不稳定"
  • 精确定义:用来放大安全库存的系数,反映需求波动性和预测误差范围
  • 取值范围:0.15 ~ 0.40(DDMRP推荐)
    • VF越低 → 需求越稳定 → 缓冲库存越少(如AX等级 VF=0.15)
    • VF越高 → 需求越波动 → 缓冲库存越多(如BZ等级 VF=0.40)
  • 计算基础:结合历史需求标准差 + 预测误差率
  • 舆情调整:GRI升高(地缘风险增加需求激增风险) → VF自动增大
  • 举例:平均日用10单位,DLT=10天,VF=0.20
    • 红区基础 = 10 × 10 = 100单位
    • 红区安全 = 100 × 0.20 = 20单位
    • 红区总计 = 120单位(这是最低库存警戒线)

「提前期系数」(Lead Time Factor, LTF)

  • 通俗理解:"供应方有多不可靠"
  • 精确定义:用来调整绿区(监控缓冲)的系数,反映交期不确定性和供应风险
  • 取值范围:0.20 ~ 0.45(DDMRP推荐)
    • LTF越低 → 供应越可靠 → 监控库存越少(如AX等级 LTF=0.20)
    • LTF越高 → 供应越不可靠 → 监控库存越多(如BZ等级 LTF=0.45)
  • 风险源:供应商交期延迟(LDI) + 合规风险(CRI) + 汇率波动(FRI)
  • 舆情调整:LDI升高(物流风险) → LTF自动增大 → 绿区自动扩大
  • ABC体现:A类高价值商品LTF更低(可靠供应才能保证销售),C类低价值LTF相对较高(成本控制优先)

ABC-XYZ矩阵商品分类与补货策略

大宗商品应采用ABC-XYZ二维矩阵分类模型,而非简单的单维ABC分类。

  • ABC维度(价值维度):基于商品价值,决定库存投入优先级
  • XYZ维度(需求维度):基于需求稳定性,决定预测方法和补货规则

ABC分类维度(基于价值 / 销售额)

等级特征占比库存投入
A类高价值、需求稳定销售额80%库存成本优化、严格周转
B类中等价值、需求中等销售额15%适度库存、定期监控
C类低价值、滞销品销售额5%最小库存、尽快清货

XYZ分类维度(基于需求波动性)

等级需求特征需求系数预测难度补货策略
X类需求稳定、波动小CV<0.2容易定期推式MRP、固定安全库存
Y类需求波动、间断性0.2<CV<0.6中等Croston法、聚合分解、动态缓冲
Z类需求剧烈、不稳定CV>0.6困难采用最小库存、按单订购(MTO)

ABC-XYZ九宫格矩阵详解

第一象限:A类商品(高价值)

AX:高价值,需求稳定

  • 核心利润来源、需求稳定、预测准确高
  • 策略:降低安全库存、释放营资金、提高资金周转效率
  • 补货规则:MRP系统、定期推式、库存周转>6次/年
  • 库存目标:绝对值最小化、周转速度最大化
  • 舆情敏感性:低(风险调整系数 0.10-0.15)

AY:高价值,需求波动

  • 价值高但需求存在波动性、预测难度中等
  • 策略:平衡库存制、适度安全库存、定期监控
  • 补货规则:Croston法与聚合分解、DDMRP缓冲、定期审视
  • 库存目标:在保持服务水平与成本间平衡
  • 舆情敏感性:中(风险调整系数 0.25-0.35)

AZ:高价值,需求剧烈波动

  • 价值高但需求极不稳定、预测难度大
  • 策略:采用保守库存、需求信号制、密切跟踪市场
  • 补货规则:采用订单驱动(MTO/ATO)、减少库存压力、关注市场动态
  • 库存目标:库存额固定、减少资金占压、快速响应
  • 舆情敏感性:高(风险调整系数 0.40-0.50)
第二象限:B类商品(中等价值)

BX:中等价值,需求稳定

  • 价值中等、需求稳定可预测
  • 策略:适度控制、定期盘点、平衡管理
  • 补货规则:定期MRP、定期购、平衡库存
  • 库存目标:库存水平与周转率都在中等水平
  • 舆情敏感性:低-中(风险调整系数 0.15-0.25)

BY:中等价值,需求波动

  • 价值中等、需求存在波动性
  • 策略:Croston法与聚合分解、定期监控
  • 补货规则:Croston法与聚合分解、定期监控
  • 库存目标:加强控制、适度安全库存、避免缺货
  • 舆情敏感性:中(风险调整系数 0.30-0.40)

BZ:中等价值,需求剧烈波动

  • 价值中等但需求波动剧烈
  • 策略:加强控制、适度安全库存、避免缺货
  • 补货规则:Croston法与聚合分解、定期监控
  • 库存目标:降低库存、增强控制、适度安全库存
  • 舆情敏感性:中-高(风险调整系数 0.35-0.50)
第三象限:C类商品(低价值)

CX:低价值,需求稳定

  • 价值贵最小、需求稳定
  • 策略:自动化补充、以最低管理成本保保供应
  • 补货规则:自动化补货、以最低管理成本保保供应
  • 库存目标:最小库存、自动补充、防止缺货
  • 舆情敏感性:极低(风险调整系数 <0.10)

CY:低价值,需求波动

  • 价值贵最小、需求波动
  • 策略:简化管理、降低持有成本
  • 补货规则:简化管理、降低持有成本
  • 库存目标:尽量避免有库存、采用按订单生产/采购模式
  • 舆情敏感性:极低(风险调整系数 <0.10)

CZ:低价值,需求剧烈波动

  • 价值贵最小、需求波动剧烈
  • 策略:尽量避免有库存、采用按订单生产/采购模式
  • 补货规则:尽量避免有库存、采用按订单生产/采购模式
  • 库存目标:零库存理想、按实际需求采购
  • 舆情敏感性:极低(风险调整系数 <0.08)

商品分类的量化标准

def classify_commodity_abc_xyz(
    annual_sales_value: float,      # 年销售额(¥)
    total_category_value: float,    # 同类产品总销售额(¥)
    demand_series: list,            # 历史需求序列(最近90天日销)
    sentiment_indices: dict = None  # 舆情指标
) -> dict:
    """
    基于ABC-XYZ矩阵对商品进行二维分类
    """
    
    # ABC分类:基于销售额占比
    sales_ratio = annual_sales_value / total_category_value
    
    if sales_ratio >= 0.80:  # 前80%销售额
        abc_grade = 'A'
    elif sales_ratio >= 0.95:  # 80%-95%
        abc_grade = 'B'
    else:  # 95%以后
        abc_grade = 'C'
    
    # XYZ分类:基于需求变异系数(Coefficient of Variation)
    demand_mean = np.mean(demand_series)
    demand_std = np.std(demand_series)
    cv = demand_std / demand_mean if demand_mean > 0 else float('inf')
    
    if cv < 0.2:
        xyz_grade = 'X'  # 稳定
    elif cv < 0.6:
        xyz_grade = 'Y'  # 波动
    else:
        xyz_grade = 'Z'  # 剧烈波动
    
    # 综合分类
    combined_grade = abc_grade + xyz_grade
    
    # 根据舆情调整分类
    if sentiment_indices:
        gri = sentiment_indices.get('gri', 0)  # 地缘政治风险
        cri = sentiment_indices.get('cri', 0)  # 合规风险
        
        # 高风险环境下,提升波动等级
        if (gri > 6 or cri > 6) and xyz_grade == 'X':
            xyz_grade = 'Y'  # X升级到Y
        elif (gri > 7 or cri > 7) and xyz_grade == 'Y':
            xyz_grade = 'Z'  # Y升级到Z
        
        combined_grade = abc_grade + xyz_grade
    
    return {
        'combined_grade': combined_grade,
        'abc_grade': abc_grade,
        'xyz_grade': xyz_grade,
        'sales_ratio': round(sales_ratio, 4),
        'cv': round(cv, 3),
        'classification_details': {
            'abc_logic': f"销售额占比{sales_ratio*100:.1f}% → {abc_grade}类",
            'xyz_logic': f"需求变异系数CV={cv:.3f}{xyz_grade}类",
            'priority': _get_priority(combined_grade),
            'management_intensity': _get_intensity(combined_grade)
        }
    }

def _get_priority(grade: str) -> str:
    """获取管理优先级"""
    priority_map = {
        'AX': 'HIGHEST',   # 最高优先级
        'AY': 'HIGH',
        'AZ': 'HIGH',
        'BX': 'MEDIUM',
        'BY': 'MEDIUM',
        'BZ': 'MEDIUM',
        'CX': 'LOW',
        'CY': 'LOW',
        'CZ': 'LOWEST'     # 最低优先级
    }
    return priority_map.get(grade, 'UNKNOWN')

def _get_intensity(grade: str) -> str:
    """获取管理强度描述"""
    intensity_map = {
        'AX': '严格库存优化、周转最大化',
        'AY': '平衡库存制、定期审视',
        'AZ': '订单驱动、快速响应',
        'BX': '适度控制、定期盘点',
        'BY': '强化控制、防止缺货',
        'BZ': '降低库存、增强控制',
        'CX': '自动补充、最小成本',
        'CY': '简化管理、降低成本',
        'CZ': '零库存、按单采购'
    }
    return intensity_map.get(grade, '未知')

舆情系统与补货的数据融合

基于ABC-XYZ矩阵分类,舆情指标需要动态调整不同类商品的补货参数:

# 舆情驱动的补货量调整

def calculate_sentiment_adjusted_replenishment_abc_xyz(
    base_forecast: float,           # 基础预测值
    sentiment_indices: dict,        # 舆情指标 {ldi, cri, gri, fri}
    commodity_grade: str,           # ABC-XYZ纵模(如'AX', 'BY', 'CZ')
    safety_stock: float             # 计算出的安全库存
) -> dict:
    """
    基于ABC-XYZ数据驱动的补货量计算
    """
    
    ldi = sentiment_indices.get('ldi', 0) / 10.0  # 物流延迟指数(0-1)
    cri = sentiment_indices.get('cri', 0) / 10.0  # 合规风险指数(0-1)
    gri = sentiment_indices.get('gri', 0) / 10.0  # 地缘政治指数(0-1)
    fri = sentiment_indices.get('fri', 0) / 10.0  # 金融风险指数(0-1)
    
    # 步骤1: 计算舆情压力指数
    sentiment_pressure = (
        0.25 * ldi +   # 物流延迟直接影响补货交期
        0.20 * cri +   # 合规风险影响成本结构
        0.35 * gri +   # 地缘政治风险驱动采购行为(权重最高)
        0.20 * fri     # 金融风险影响订单确定性
    )
    
    # 步骤2: ABC-XYZ矩阵中,不同的商品有不同的风险敏感性
    # ABC维度(A/B/C)决定库存成本敏感性
    # XYZ维度(X/Y/Z)决定需求波动敏感性
    
    risk_sensitivity_map = {
        # A类:高价值,低成本敏感性
        'AX': 0.10,    # 高价值(低效合,低波动)
        'AY': 0.25,    # 中高价值、中波动
        'AZ': 0.40,    # 高价值、高波动、平衡库存
        
        # B类:中等价值,中等成本敏感性
        'BX': 0.15,    # 中低价值、稳定需求
        'BY': 0.35,    # 中低价值、中波动需求
        'BZ': 0.50,    # 中低价值、高波动需求
        
        # C类:低价值,低成本敏感性(极低需求)
        'CX': 0.08,    # 低价值、稳定需求
        'CY': 0.10,    # 低价值、中波动
        'CZ': 0.12     # 低价值、高波动、最小库存
    }
    
    risk_multiplier = risk_sensitivity_map.get(commodity_grade, 0.2)
    risk_adjusted = 1.0 + (sentiment_pressure * risk_multiplier)
    
    # 步骤3: 计算调整后的安全库存
    adjusted_safety_stock = safety_stock * risk_adjusted
    
    # 步骤4: 计算最终补货量
    # GRI触发的需求激增预期
    demand_surge_multiplier = 1.0
    
    # ABC-XYZ不同等级的GRI敦值不同
    if commodity_grade in ['AX', 'AY', 'BX']:  # 低风险哦值
        gri_threshold = 0.75
        gri_factor = 0.4
    elif commodity_grade in ['AZ', 'BY', 'BZ', 'CZ']:  # 中高风险哦值
        gri_threshold = 0.50
        gri_factor = 0.6
    else:  # C类低价值商品不调整GRI
        gri_threshold = 0.9
        gri_factor = 0.2
    
    if gri > gri_threshold:
        # 高地缘政治风险,预期采购商囊积行为
        demand_surge_multiplier = 1.0 + (gri - gri_threshold) * gri_factor
    
    final_replenishment = (
        base_forecast * demand_surge_multiplier + adjusted_safety_stock
    )
    
    return {
        'commodity_grade': commodity_grade,
        'base_forecast': round(base_forecast, 2),
        'sentiment_pressure_score': round(sentiment_pressure, 3),
        'risk_multiplier': risk_multiplier,
        'adjusted_safety_stock': round(adjusted_safety_stock, 2),
        'demand_surge_multiplier': round(demand_surge_multiplier, 3),
        'final_replenishment_qty': round(final_replenishment, 2),
        'adjustment_details': {
            'ldi_impact': f"物流延迟{ldi:.1f}: {'{交期风险高}' if ldi > 0.5 else '交期正常'}",
            'gri_impact': f"地缘风险{gri:.1f}: {'{挀幝可能}' if gri > 0.5 else '低风险'}",
            'cri_impact': f"合规风险{cri:.1f}: {'{成本压力高}' if cri > 0.6 else '浅水低'}",
            'fri_impact': f"金融风险{fri:.1f}: {'{供应商认信兴趣高}' if fri > 0.5 else '供应商夷体'}",
            'replenishment_strategy': _get_replenishment_strategy(commodity_grade, sentiment_pressure)
        }
    }

def _get_replenishment_strategy(grade: str, pressure: float) -> str:
    """根据商品等级和舆情压力订制补货策略"""
    if grade.startswith('A'):
        return '优先保供、优先收款、严格控制资金'
    elif grade.startswith('B'):
        if grade.endswith('X'):
            return '正常补货、适度安全库'
        else:
            return '优先补货、一接一订'
    else:  # C类
        return '低优先级、按需采购、清货业务'

不同安全库存方法的对比

静态安全库存 vs 动态安全库存

特性静态安全库存 (Static SS)动态安全库存 (DSS)
定义基于历史平均值的固定库存缓冲。基于实时数据和市场信号的自适应库存策略。
计算方法简单公式 (如2%规则) 和历史数据。实时数据流、算法、机器学习和统计分析。
更新频率按季度或年度进行审查。基于当前条件持续进行实时校准。
市场响应滞后被动。积极主动。
主要用例市场稳定、SKU组合有限。市场波动、易损产品、复杂供应链和大型SKU组合。
优势实施简单高效。减少缺货和过剩库存,提高服务水平和成本效益。
劣势波动期易丧失效果或导致库存过剩风险。需要对技术和培训进行投资。

动态安全库存的核心计算

动态安全库存的计算需要结合以下关键因素:

def calculate_dss_comprehensive(
    demand_series: np.ndarray,           # 历史需求时间序列
    forecast_errors: np.ndarray,         # 预测误差历史
    lead_time_days: int,                 # 交期时间
    sentiment_pressure: float,           # 舆情压力指数 (0-1)
    commodity_grade: str,                # A/B/C等级
    service_level: float = 0.95          # 服务水平
) -> dict:
    """
    流程、预测误差和舆情指标综合动态安全库存计算。
    
    核心公式:
    DSS = Z×σ_combined×√L + 舆情调整
    
    其中:
    - Z: 服务水平系数
    - σ_combined: 组合的不确定性 (需求+执行)
    - L: 交期平方开方
    - 舆情调整: 基于风险指数
    """
    
    # 1. 计算需求波动 (标准差)
    demand_std = np.std(demand_series)
    demand_mean = np.mean(demand_series)
    
    # 2. 计算执行不确定 (预测误差)
    execution_uncertainty = np.std(forecast_errors)
    
    # 3. 组合不确定性 (综合不确定)
    combined_uncertainty = np.sqrt(
        demand_std**2 + execution_uncertainty**2
    )
    
    # 4. 服务水平z值
    z_scores = {0.90: 1.28, 0.95: 1.645, 0.99: 2.33}
    z = z_scores.get(service_level, 1.645)
    
    # 5. 基础DSS
    base_dss = z * combined_uncertainty * np.sqrt(lead_time_days)
    
    # 6. 舆情调整 (不同等级的敏感性不同)
    sentiment_multiplier = {
        'A': 0.10,   # A类低敏感
        'B': 0.35,   # B类中敏感
        'C': 0.05    # C类极低敏感
    }
    
    sentiment_buffer = sentiment_pressure * base_dss * sentiment_multiplier.get(commodity_grade, 0.15)
    dss = base_dss + sentiment_buffer
    
    return {
        'dss': round(dss, 2),
        'base_dss': round(base_dss, 2),
        'sentiment_buffer': round(sentiment_buffer, 2),
        'combined_uncertainty': round(combined_uncertainty, 2),
        'demand_std': round(demand_std, 2),
        'execution_uncertainty': round(execution_uncertainty, 2)
    }

DDMRP模型与B等商品的具体应用

DDMRP概述

需求驱动的物料需求计划 (DDMRP) 是一个前沿的及时补货方法,基于"拉动"的库存策略。与传统的基于预测的"推动"MRP方法形成了明显对照。

DDMRP的核心与其"定位、保护、拉动"样式形成的三个工作流程:

  1. 定位 (Position):在物料清单 (BOM) 中的关键路径物料或组件处设置库存缓冲。
  2. 保护 (Protect):利用安全库存来吸收需求和供应的波动,保证生产计划。
  3. 拉动 (Pull):根据实际需求和库存水平,动态地拉动供给,而非根据固定的预测计划。

DDMRP缓冲区的ABC-XYZ分级配置

缓冲区配置是DDMRP的核心,其VF、LTF参数必须根据商品所处的ABC-XYZ等级进行分级设置。不同等级的基础参数和舆情敏感性差异巨大:

缓冲区参数的ABC-XYZ分级矩阵

ABC-XYZ等级与VF/LTF基础值对应表:

            VF(可变性系数)              LTF(提前期系数)
            需求波动容限               交期不确定性
            
AX  0.15 → AX  0.20  (最低保守)     (高可靠)
AY  0.25    AY  0.30
AZ  0.35    AZ  0.40

BX  0.20    BX  0.25
BY  0.30    BY  0.35
BZ  0.40    BZ  0.45  (最激进)

CX  0.15    CX  0.20
CY  0.25    CY  0.30
CZ  0.20    CZ  0.25  (C类简化管理)

策略原则

  • A类商品 VF/LTF最低:价值大,不容许频繁缺货或过量库存
  • B类商品 VF/LTF中等:平衡成本与服务水平
  • C类商品 VF/LTF特殊:低价值,更注重成本控制而非精细管理
  • XYZ维度 按波动性线性增加:X<Y<Z,体现需求稳定性差异

ABC-XYZ适配的DDMRP缓冲区计算代码

def calculate_ddmrp_buffer_parameters_abc_xyz(
    adu: float,                      # 平均日用量
    dlt: int,                        # 解耦提前期 (天)
    commodity_grade: str,            # ABC-XYZ等级 ('AX','BY','CZ'等)
    sentiment_indices: dict = None,  # 舆情指标
) -> dict:
    """
    基于ABC-XYZ矩阵的DDMRP缓冲区参数计算
    """
    
    # 步骤1: ABC-XYZ基础VF/LTF矩阵
    vf_matrix = {
        'AX': 0.15, 'AY': 0.25, 'AZ': 0.35,
        'BX': 0.20, 'BY': 0.30, 'BZ': 0.40,
        'CX': 0.15, 'CY': 0.25, 'CZ': 0.20
    }
    
    ltf_matrix = {
        'AX': 0.20, 'AY': 0.30, 'AZ': 0.40,
        'BX': 0.25, 'BY': 0.35, 'BZ': 0.45,
        'CX': 0.20, 'CY': 0.30, 'CZ': 0.25
    }
    
    vf = vf_matrix.get(commodity_grade, 0.25)
    ltf = ltf_matrix.get(commodity_grade, 0.30)
    
    # 步骤2: 舆情驱动的动态调整(分等级调整幅度)
    if sentiment_indices:
        gri = sentiment_indices.get('gri', 0) / 10  # 0-1
        ldi = sentiment_indices.get('ldi', 0) / 10
        cri = sentiment_indices.get('cri', 0) / 10
        
        # VF调整:GRI激发需求波动
        # A/B低波动商品敏感性高,C类商品敏感性低
        if commodity_grade in ['AX', 'AY', 'BX']:  # 低波动等级
            vf += gri * 0.20  # GRI最多增加+0.20
        elif commodity_grade in ['AZ', 'BY', 'BZ']:  # 中波动等级
            vf += gri * 0.15  # GRI最多增加+0.15
        else:  # C类低价值商品
            vf += gri * 0.08  # GRI最多增加+0.08
        
        # LTF调整:LDI/CRI激发交期不确定性
        # 所有等级都会受影响,但程度不同
        ldi_impact = ldi * 0.15  # LDI影响最大
        cri_impact = cri * 0.10   # CRI影响次要
        ltf += ldi_impact + cri_impact  # LTF最多增加+0.25
        
        # XYZ维度对舆情的进一步放大(Z等级对舆情最敏感)
        if commodity_grade.endswith('Z'):  # Z等级:高波动
            vf *= 1.2  # VF额外增加20%
            ltf *= 1.2  # LTF额外增加20%
        elif commodity_grade.endswith('Y'):
            vf *= 1.1  # VF额外增加10%
            ltf *= 1.1  # LTF额外增加10%
        # X等级不额外放大
    
    # 步骤3: 参数范围约束
    vf = min(vf, 1.0)  # VF最多1.0
    ltf = min(ltf, 1.0)  # LTF最多1.0
    
    # 步骤4: 计算缓冲区参数
    red_base = adu * dlt
    red_safety = red_base * vf
    red_zone = red_base + red_safety
    
    yellow_zone = red_zone
    green_zone = red_base * ltf * 0.5  # 绿区受LTF影响
    
    min_stock = red_zone
    max_stock = red_zone + yellow_zone + green_zone
    reorder_point = red_zone + yellow_zone
    
    # 步骤5: 获取补货模式描述
    replenishment_mode = {
        'AX': 'Push-MRP / 周期推式,固定安全库存',
        'AY': 'Push-MRP + DDMRP / 动态缓冲,平衡库存',
        'AZ': 'ATO / 订单驱动,最小库存',
        'BX': 'MRP控制 / 定时盘点,平衡库存',
        'BY': 'Croston + DDMRP / 间断需求预测,动态缓冲',
        'BZ': 'DDMRP三层缓冲 / 强化控制,防止缺货',
        'CX': '自动补充 / 最小库存,成本最优',
        'CY': '低控制 / 简化管理,清货优先',
        'CZ': 'MTO / 按单采购,零库存目标'
    }
    
    return {
        'commodity_grade': commodity_grade,
        'adu': round(adu, 2),
        'dlt': dlt,
        'base_vf': round(vf_matrix.get(commodity_grade, 0.25), 3),
        'base_ltf': round(ltf_matrix.get(commodity_grade, 0.30), 3),
        'adjusted_vf': round(vf, 3),
        'adjusted_ltf': round(ltf, 3),
        'buffer_zones': {
            'red_base': round(red_base, 2),
            'red_safety': round(red_safety, 2),
            'red_total': round(red_zone, 2),
            'yellow': round(yellow_zone, 2),
            'green': round(green_zone, 2)
        },
        'inventory_levels': {
            'min_stock': round(min_stock, 2),
            'max_stock': round(max_stock, 2),
            'reorder_point': round(reorder_point, 2)
        },
        'replenishment_mode': replenishment_mode.get(commodity_grade, '未知'),
        'buffer_summary': {
            'total_buffer': round(red_zone + yellow_zone + green_zone, 2),
            'sentiment_adjusted': sentiment_indices is not None
        }
    }

实时库存触发补货机制(保持不变)

系统应根据净流位置(Net Flow Position)实时触发补货:

def trigger_replenishment_abc_xyz(
    net_flow_position: float,        # 当前净流位置
    buffer_params: dict,              # DDMRP缓冲区参数
    commodity_grade: str             # ABC-XYZ等级
) -> dict:
    """
    ABC-XYZ等级适配的实时补货触发
    不同等级的补货优先级策略不同
    """
    
    reorder_point = buffer_params['reorder_point']
    max_stock = buffer_params['max_stock']
    min_stock = buffer_params['min_stock']
    
    trigger_action = 'NO_ACTION'
    replenishment_qty = 0
    priority = 'NORMAL'
    
    # ABC-XYZ等级对应的优先级映射
    priority_map = {
        'AX': {'CRITICAL': 'CRITICAL', 'HIGH': 'HIGH', 'NORMAL': 'NORMAL'},
        'AY': {'CRITICAL': 'CRITICAL', 'HIGH': 'HIGH', 'NORMAL': 'MEDIUM'},
        'AZ': {'CRITICAL': 'HIGH', 'HIGH': 'HIGH', 'NORMAL': 'MEDIUM'},  # 高价值波动商品优先级稍低
        'BX': {'CRITICAL': 'HIGH', 'HIGH': 'MEDIUM', 'NORMAL': 'NORMAL'},
        'BY': {'CRITICAL': 'HIGH', 'HIGH': 'MEDIUM', 'NORMAL': 'NORMAL'},
        'BZ': {'CRITICAL': 'HIGH', 'HIGH': 'MEDIUM', 'NORMAL': 'LOW'},     # B类波动商品缺货控制重要
        'CX': {'CRITICAL': 'MEDIUM', 'HIGH': 'LOW', 'NORMAL': 'LOW'},
        'CY': {'CRITICAL': 'LOW', 'HIGH': 'LOW', 'NORMAL': 'LOW'},
        'CZ': {'CRITICAL': 'LOW', 'HIGH': 'LOW', 'NORMAL': 'LOW'}         # C类商品优先级最低
    }
    
    if net_flow_position < min_stock:
        trigger_action = 'URGENT_REPLENISH'
        replenishment_qty = max_stock - net_flow_position
        priority = priority_map.get(commodity_grade, {}).get('CRITICAL', 'CRITICAL')
    
    elif net_flow_position < reorder_point:
        trigger_action = 'PLANNED_REPLENISH'
        replenishment_qty = max_stock - net_flow_position
        priority = priority_map.get(commodity_grade, {}).get('HIGH', 'HIGH')
    
    elif net_flow_position < max_stock:
        trigger_action = 'MONITOR'
        replenishment_qty = max_stock - net_flow_position
        priority = priority_map.get(commodity_grade, {}).get('NORMAL', 'NORMAL')
    
    else:
        trigger_action = 'NO_ACTION'
        replenishment_qty = 0
        priority = 'LOW'
    
    return {
        'commodity_grade': commodity_grade,
        'net_flow_position': round(net_flow_position, 2),
        'trigger_action': trigger_action,
        'replenishment_qty': round(replenishment_qty, 2),
        'priority': priority,
        'zone_status': {
            'red_zone_threshold': buffer_params['red_zone']['total'],
            'yellow_zone_threshold': buffer_params['reorder_point'],
            'current_position_zone': _determine_zone(net_flow_position, buffer_params)
        }
    }

def _determine_zone(position: float, params: dict) -> str:
    """确定当前库存位于哪个区域"""
    min_stock = params['min_stock']
    reorder_point = params['reorder_point']
    
    if position < min_stock:
        return 'RED_ZONE (Critical)'
    elif position < reorder_point:
        return 'YELLOW_ZONE (Warning)'
    else:
        return 'GREEN_ZONE (Safe)'

B等商品应用案例

背景:某生产企业生产长尾商品,月需求也是1-2次/月,每次销量也非常低。传统的MRP计算虽然看起来合理,但实际补货严重不足,反而会残留大量库存。

DDMRP实施策略

案例1:间断需求商品(B类)的DDMRP应用

# 案例场景:特殊规格钢板,月需求1-2次

from datetime import datetime
import pandas as pd
import numpy as np

def ddmrp_replenishment_workflow(
    sku: str,
    current_net_flow: float,
    historical_demand: list,
    sentiment_indices: dict,
    supplier_config: dict
):
    """
    完整的DDMRP补货工作流程
    """
    
    # 步骤1: 计算平均日用量(ADU)
    adu = np.mean(historical_demand)
    demand_std = np.std(historical_demand)
    
    # 步骤2: 确定解耦提前期(DLT)
    dlt = supplier_config['lead_time_days']
    
    # 步骤3: 确定可变性系数(VF)和提前期系数(LTF)
    # B类商品:基础VF=0.5, 基础LTF=0.5
    base_vf = 0.5 if demand_std / adu > 0.3 else 0.3
    base_ltf = 0.5
    
    # 步骤4: 计算DDMRP缓冲区参数(含舆情调整)
    buffer_params = calculate_ddmrp_buffer_parameters(
        adu=adu,
        dlt=dlt,
        base_vf=base_vf,
        base_ltf=base_ltf,
        sentiment_indices=sentiment_indices,
        commodity_grade='B'  # B类商品
    )
    
    # 步骤5: 检查当前库存位置并触发补货
    replenishment_decision = trigger_replenishment(
        net_flow_position=current_net_flow,
        buffer_params=buffer_params
    )
    
    # 步骤6: 生成补货建议
    recommendation = {
        'sku': sku,
        'timestamp': datetime.now().isoformat(),
        'current_position': current_net_flow,
        'buffer_configuration': buffer_params,
        'replenishment_decision': replenishment_decision,
        'supplier_recommendation': {
            'supplier': supplier_config['name'],
            'suggested_order_qty': replenishment_decision['replenishment_qty'],
            'order_cost': replenishment_decision['replenishment_qty'] * supplier_config['unit_price'],
            'lead_time': supplier_config['lead_time_days'],
            'urgency': replenishment_decision['priority']
        },
        'expected_outcomes': {
            'inventory_reduction': f"{(buffer_params['min_stock'] - buffer_params['red_zone']['total']) / buffer_params['min_stock'] * 100:.1f}%",
            'service_level_maintained': '>=95%',
            'replenishment_frequency': f"每月{30 / dlt:.0f}次左右"
        }
    }
    
    return recommendation

# 实际应用示例
if __name__ == '__main__':
    # B类商品:特殊规格钢板
    historical_demand = [
        0, 0, 50, 0, 0, 0, 60, 0, 0, 45,
        0, 0, 0, 55, 0, 0, 0, 0, 58, 0
    ]  # 20天的销售数据,存在大量零点
    
    recommendation = ddmrp_replenishment_workflow(
        sku='STEEL_PLATE_SPECIAL_001',
        current_net_flow=150,  # 当前库存水平
        historical_demand=historical_demand,
        sentiment_indices={
            'ldi': 4,  # 物流延迟中等
            'cri': 5,  # 合规风险中等
            'gri': 2,  # 地缘政治风险低
            'fri': 3   # 金融风险低
        },
        supplier_config={
            'name': '宝山供应商B',
            'unit_price': 3100,
            'lead_time_days': 7,
            'capacity': 500,
            'moq': 50
        }
    )
    
    print("\n=== DDMRP补货建议 ===")
    print(f"SKU: {recommendation['sku']}")
    print(f"当前库存位置: {recommendation['current_position']}")
    print(f"\n缓冲区配置:")
    print(f"  红区总计: {recommendation['buffer_configuration']['red_zone']['total']}")
    print(f"  黄区: {recommendation['buffer_configuration']['yellow_zone']}")
    print(f"  绿区: {recommendation['buffer_configuration']['green_zone']}")
    print(f"  最小库存值: {recommendation['buffer_configuration']['min_stock']}")
    print(f"  最大库存值: {recommendation['buffer_configuration']['max_stock']}")
    print(f"\n补货决策:")
    print(f"  触发动作: {recommendation['replenishment_decision']['trigger_action']}")
    print(f"  建议订购量: {recommendation['replenishment_decision']['replenishment_qty']} 单位")
    print(f"  优先级: {recommendation['replenishment_decision']['priority']}")
    print(f"  当前区域: {recommendation['replenishment_decision']['zone_status']['current_position_zone']}")
    print(f"\n供应商建议:")
    print(f"  供应商: {recommendation['supplier_recommendation']['supplier']}")
    print(f"  订单总成本: ¥{recommendation['supplier_recommendation']['order_cost']:.2f}")
    print(f"  预计效果:")
    print(f"    库存水平降低: {recommendation['expected_outcomes']['inventory_reduction']}")
    print(f"    服务水平: {recommendation['expected_outcomes']['service_level_maintained']}")
    print(f"    补货频率: {recommendation['expected_outcomes']['replenishment_frequency']}")

关键要点

  1. 动态缓冲区 - 利用VF和LTF的舆情调整,使缓冲区能够主动应对风险
  2. 实时触发 - 基于净流位置的触发机制替代定期补货,反应更敏捷
  3. 三层分层 - 红区(紧急)、黄区(计划)、绿区(监控),清晰的决策规则
  4. B类商品优化 - 特别适合间断需求,相比Croston法更稳健
  5. 舆情融合 - 通过LDI/CRI/GRI/FRI指标动态调整参数,主动防范风险

PuLP求解器实现

完整的优化引擎

from pulp import *
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class IntelligentReplenishmentEngine:
    """
    智能补货决策引擎
    - 整合舆情指标优化安全库存
    - 多供应商模式应对断裂风险
    - 考虑资金成本与缺货成本的权衡
    """
    
    def __init__(self, commodity_code: str, buyer_geo_key: str):
        self.commodity_code = commodity_code  # 商品代码
        self.buyer_geo_key = buyer_geo_key   # 采购商地理位置
        self.problem = None
        self.decision_vars = {}
        
    def calculate_safety_stock(self, 
                               avg_demand: float,
                               demand_std: float,
                               lead_time_days: int,
                               service_level: float = 0.95,
                               sentiment_indices: dict = None) -> float:
        """
        计算风险调整后的安全库存
        
        基础安全库存公式 (EOQ变体):
          SS = Z_α × σ_demand × sqrt(L)
          
        舆情调整:
          - LDI增高 → lead_time增加 → SS↑
          - GRI增高 → 需求波动增加 → SS↑
          - CRI增高 → 供应不确定↑ → SS↑
        """
        
        # 服务水平对应的标准差倍数
        z_scores = {0.90: 1.28, 0.95: 1.645, 0.99: 2.33}
        z = z_scores.get(service_level, 1.645)
        
        # 基础安全库存
        base_ss = z * demand_std * np.sqrt(lead_time_days)
        
        # 舆情调整因子
        adjustment_factor = 1.0
        if sentiment_indices:
            # LDI增加的配送时间
            if 'ldi' in sentiment_indices:
                ldi_adjusted_lead_time = lead_time_days * (1 + sentiment_indices['ldi'] / 10 * 0.3)
                base_ss = z * demand_std * np.sqrt(ldi_adjusted_lead_time)
            
            # GRI增加的需求不确定性
            if 'gri' in sentiment_indices:
                gri_factor = sentiment_indices['gri'] / 10  # 0-1
                demand_std_adjusted = demand_std * (1 + gri_factor * 0.4)
                base_ss = z * demand_std_adjusted * np.sqrt(lead_time_days)
            
            # CRI增加的供应风险
            if 'cri' in sentiment_indices:
                cri_factor = sentiment_indices['cri'] / 10
                adjustment_factor *= (1 + cri_factor * 0.2)
        
        safety_stock = base_ss * adjustment_factor
        
        return safety_stock
    
    def optimize_replenishment(self,
                               current_inventory: float,
                               forecast_demand: np.ndarray,  # 未来30天的日需求
                               holding_cost_per_unit: float,  # 库存成本 (¥/单位/天)
                               stockout_cost_per_unit: float,  # 缺货成本 (¥/单位)
                               suppliers: list,  # 供应商列表
                               sentiment_indices: dict = None) -> dict:
        """
        使用整数线性规划求解最优补货方案
        
        参数示例:
          current_inventory: 100 (吨)
          forecast_demand: [50, 55, 45, ..., 60]  (30天)
          holding_cost: 50 (¥/吨/天)
          stockout_cost: 2000 (¥/吨)
          suppliers: [
            {
              'name': '供应商A',
              'unit_price': 3000,  # ¥/吨
              'lead_time': 5,  # 天
              'capacity': 500,  # 吨/月
              'moq': 50,  # 最小订购量
              'quality_score': 0.95,
              'reliability': 0.9
            },
            ...
          ]
        """
        
        days = len(forecast_demand)
        n_suppliers = len(suppliers)
        
        # 创建LP问题
        prob = LpProblem("Replenishment_Optimization", LpMinimize)
        
        # 决策变量
        # x[i][j]: 从供应商j在第i天的订购量 (吨)
        x = [[LpVariable(f"order_day{i}_supplier{j}", lowBound=0, cat='Continuous')
              for j in range(n_suppliers)] for i in range(days)]
        
        # h[i]: 第i天末的库存量 (吨)
        h = [LpVariable(f"inventory_day{i}", lowBound=0, cat='Continuous') for i in range(days)]
        
        # s[i]: 第i天的缺货量 (吨)
        s = [LpVariable(f"shortage_day{i}", lowBound=0, cat='Continuous') for i in range(days)]
        
        # y[i][j]: 是否从供应商j在第i天订购 (二元变量, 用于MOQ约束)
        y = [[LpVariable(f"order_flag_day{i}_supplier{j}", cat='Binary')
              for j in range(n_suppliers)] for i in range(days)]
        
        # ===== 目标函数 =====
        # 最小化: 库存持有成本 + 缺货损失 + 订货次数溢价 (多源溢价)
        cost_holding = lpSum([h[i] * holding_cost_per_unit for i in range(days)])
        cost_shortage = lpSum([s[i] * stockout_cost_per_unit for i in range(days)])
        
        # 多源溢价 (从>1个供应商订购时增加成本5%)
        multi_source_premium = lpSum([
            y[i][j] * suppliers[j]['unit_price'] * 0.05 * lpSum([x[i][j_] for j_ in range(n_suppliers)])
            for i in range(days) for j in range(n_suppliers)
        ])
        
        prob += cost_holding + cost_shortage + multi_source_premium, "Total_Cost"
        
        # ===== 约束条件 =====
        
        # 1. 库存平衡约束
        for i in range(days):
            if i == 0:
                # 第一天: 当前库存 + 订单 - 需求 = 库存 + 缺货
                prob += (
                    current_inventory + 
                    lpSum([x[i][j] for j in range(n_suppliers)]) -
                    forecast_demand[i] == h[i] + s[i],
                    f"Balance_Day{i}"
                )
            else:
                # 后续天: 前日库存 + 订单 - 需求 = 库存 + 缺货
                prob += (
                    h[i-1] + 
                    lpSum([x[i][j] for j in range(n_suppliers)]) -
                    forecast_demand[i] == h[i] + s[i],
                    f"Balance_Day{i}"
                )
        
        # 2. 最小订购量 (MOQ) 约束
        for i in range(days):
            for j in range(n_suppliers):
                moq = suppliers[j]['moq']
                # 如果订购,则订购量 >= MOQ
                prob += (
                    x[i][j] >= moq * y[i][j],
                    f"MOQ_Day{i}_Supplier{j}"
                )
                # 最大订购量 (供应商容量)
                prob += (
                    x[i][j] <= suppliers[j]['capacity'] * y[i][j],
                    f"Capacity_Day{i}_Supplier{j}"
                )
        
        # 3. 安全库存约束 (风险调整)
        avg_demand = np.mean(forecast_demand)
        demand_std = np.std(forecast_demand)
        
        # 计算风险调整的安全库存
        safety_stock = self.calculate_safety_stock(
            avg_demand, demand_std, 
            lead_time_days=suppliers[0]['lead_time'],  # 使用最快的供应商
            sentiment_indices=sentiment_indices
        )
        
        # 平均库存 >= 安全库存
        avg_inventory = lpSum([h[i] for i in range(days)]) / days
        prob += avg_inventory >= safety_stock, "Safety_Stock_Constraint"
        
        # 4. 供应商可靠性约束 (考虑舆情风险)
        for i in range(days):
            for j in range(n_suppliers):
                # 高风险环境下,不完全依赖单一供应商
                if sentiment_indices and sentiment_indices.get('cri', 0) > 6:
                    # CRI>6时,单个供应商订购量不超过预测需求的60%
                    prob += (
                        x[i][j] <= forecast_demand[i] * 0.6,
                        f"Multi_Source_Diversity_Day{i}_Supplier{j}"
                    )
        
        # 5. 缺货容忍度约束 (特定采购商)
        # 采购商等级A: 不允许缺货 (s[i]=0)
        # 采购商等级B: 缺货<平均需求的5%
        # 采购商等级C: 缺货<平均需求的10%
        
        buyer_grades = {'A': 0.0, 'B': 0.05, 'C': 0.10}
        buyer_grade = 'B'  # 默认B级
        max_shortage_ratio = buyer_grades.get(buyer_grade, 0.05)
        
        prob += (
            lpSum([s[i] for i in range(days)]) <= avg_demand * max_shortage_ratio * days,
            "Shortage_Tolerance"
        )
        
        # ===== 求解 =====
        prob.solve(PULP_CBC_CMD(msg=0))
        
        # ===== 提取结果 =====
        replenishment_plan = []
        for i in range(days):
            day_orders = []
            for j in range(n_suppliers):
                order_qty = x[i][j].varValue
                if order_qty and order_qty > 0.1:  # 订购量>0.1单位
                    day_orders.append({
                        'supplier': suppliers[j]['name'],
                        'quantity': round(order_qty, 2),
                        'unit_price': suppliers[j]['unit_price'],
                        'total_cost': round(order_qty * suppliers[j]['unit_price'], 2),
                        'lead_time': suppliers[j]['lead_time']
                    })
            
            if day_orders:  # 只记录有订单的天
                replenishment_plan.append({
                    'day': i + 1,
                    'orders': day_orders,
                    'inventory_end_of_day': round(h[i].varValue, 2),
                    'shortage': round(s[i].varValue, 2)
                })
        
        # 总成本分析
        total_cost = value(prob.objective)
        holding_cost_actual = sum([h[i].varValue * holding_cost_per_unit for i in range(days)])
        shortage_cost_actual = sum([s[i].varValue * stockout_cost_per_unit for i in range(days)])
        
        return {
            'status': LpStatus[prob.status],
            'optimization_successful': LpStatus[prob.status] == 'Optimal',
            'total_cost': round(total_cost, 2),
            'cost_breakdown': {
                'holding_cost': round(holding_cost_actual, 2),
                'shortage_cost': round(shortage_cost_actual, 2),
                'multi_source_premium': round(total_cost - holding_cost_actual - shortage_cost_actual, 2)
            },
            'replenishment_plan': replenishment_plan,
            'summary': {
                'total_orders_days': len(replenishment_plan),
                'avg_inventory_level': round(np.mean([h[i].varValue for i in range(days)]), 2),
                'total_shortage': round(sum([s[i].varValue for i in range(days)]), 2),
                'safety_stock_calculated': round(safety_stock, 2)
            }
        }

使用示例

if __name__ == '__main__':
    engine = IntelligentReplenishmentEngine(
        commodity_code='STEEL_REBAR',
        buyer_geo_key='CHINA_EAST_001'
    )
    
    # 供应商配置
    suppliers = [
        {
            'name': '鞍山供应商A',
            'unit_price': 3000,
            'lead_time': 3,
            'capacity': 500,
            'moq': 50,
            'quality_score': 0.95,
            'reliability': 0.92
        },
        {
            'name': '宝山供应商B',
            'unit_price': 3100,
            'lead_time': 5,
            'capacity': 400,
            'moq': 40,
            'quality_score': 0.90,
            'reliability': 0.85
        },
        {
            'name': '日本进口供应商C',
            'unit_price': 3500,
            'lead_time': 20,
            'capacity': 200,
            'moq': 100,
            'quality_score': 0.98,
            'reliability': 0.95
        }
    ]
    
    # 舆情数据
    sentiment = {
        'ldi': 5,   # 物流延迟中等
        'cri': 6,   # 合规风险高
        'gri': 3,   # 地缘政治风险低
        'fri': 4    # 金融风险中等
    }
    
    # 预测需求 (30天)
    forecast = np.array([
        50, 55, 45, 60, 52, 48, 65, 70, 55, 50,
        58, 62, 51, 49, 67, 72, 58, 53, 61, 68,
        54, 50, 59, 64, 52, 47, 63, 69, 55, 51
    ])
    
    # 求解
    result = engine.optimize_replenishment(
        current_inventory=100,
        forecast_demand=forecast,
        holding_cost_per_unit=50,
        stockout_cost_per_unit=2000,
        suppliers=suppliers,
        sentiment_indices=sentiment
    )
    
    print("优化结果:")
    print(f"  优化状态: {result['status']}")
    print(f"  总成本: ¥{result['total_cost']}")
    print(f"  其中: 库存成本 ¥{result['cost_breakdown']['holding_cost']}, " +
          f"缺货成本 ¥{result['cost_breakdown']['shortage_cost']}")
    print(f"  采购计划: 共{result['summary']['total_orders_days']}天有订单")

后续扩展方向

  1. 供应链可视化 - 实时库存热力图、区域预警仪表板
  2. 成本优化 - 仓储费用、缺货机会成本、滞销贾值
  3. 多层级BOM - 扩展到生产型企业的多层物料规划
  4. 外部数据融合 - 天气、事件预测增强舆情预测准确度
  5. AI自适应 - 利用历史数据自动学习最优的VF/LTF参数