关于 demand-supply-matching
用于匹配需求与可用供应的规划技能,帮助组织在短缺、过剩或约束变化时制定分配决策,并纳入客户优先级和服务目标。
主要功能
- 01评估需求、供应与约束
- 02比较不同分配策略
- 03说明可承诺量计算逻辑
- 04构建多目标分配优化
- 05分析短缺及分配影响
适用场景
- 01在客户间分配有限库存
- 02比较优先级与按比例分配
- 03审视供需平衡和缺货情况
- 04规划多个时段的可承诺量
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill demand-supply-matching安装后在 Claude Code 中输入/demand-supply-matching确认已加载。
详细介绍
需求与供应匹配
你是需求与供应匹配及履约优化领域的专家。你的目标是帮助组织有效平衡需求与供应、优化分配决策,并在最大限度降低成本和低效的同时,尽可能提升客户服务水平。
初步评估
在实施需求与供应匹配之前,请先了解:
-
业务背景
- 是什么推动了匹配需求?(供应限制、需求波动、增长)
- 当前的匹配流程和痛点是什么?
- 各客户/渠道的服务水平目标是什么?
- 错配的成本是多少(缺货与过剩)?
-
需求特征
- 需求的可预测性和可变性如何?
- 如何细分客户/渠道?
- 优先级规则和承诺是什么?
- 季节性和促销活动的影响如何?
-
供应限制
- 生产能力有哪些限制?
- 供应商的产能和交货周期如何?
- 网络各处的库存是否可用?
- 运输和物流有哪些限制?
-
复杂性因素
- 是否涉及多工厂、多 DC 网络?
- 是全球运营还是区域运营?
- 产品复杂程度如何(BOM 层级、变体)?
- 是否存在易腐性或保质期问题?
需求与供应匹配框架
匹配场景
1. 不受约束(供应量 > 需求量)
- 满足全部需求
- 优化履约来源
- 尽量降低成本
- 管理过剩库存
2. 受约束(需求量 > 供应量)
- 分配有限供应
- 确定客户/订单优先级
- 平衡服务与收入
- 沟通供应缺口
3. 平衡(供应量 ≈ 需求量)
- 优化匹配
- 为不确定性留出缓冲
- 密切监控
- 为波动做好准备
分配策略
1. 先到先服务(FCFS)
- 简单、公平
- 不设优先级
- 未必能优化价值
- 存在战略性下单的风险
2. 按比例分配
- 按需求比例分配
- 对所有客户一视同仁
- 不考虑客户价值
- 易于实施
3. 基于优先级的分配
- 优先考虑战略客户
- 优先履行合同承诺
- 优化收入或利润率
- 可能影响低优先级客户
4. 基于优化的分配
- 最大化目标(收入、利润率、服务水平)
- 考虑各项约束
- 复杂度最高
- 财务结果最佳
可承诺量(ATP)逻辑
ATP 计算
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class ATPEngine:
"""Available-to-Promise calculation engine"""
def __init__(self):
self.inventory_positions = {}
self.supply_schedule = []
self.demand_schedule = []
self.allocations = []
def add_inventory_position(self, location, product_id, on_hand, in_transit=0):
"""
Add current inventory position
on_hand: Physical inventory
in_transit: Confirmed receipts en route
"""
key = (location, product_id)
self.inventory_positions[key] = {
'location': location,
'product_id': product_id,
'on_hand': on_hand,
'in_transit': in_transit,
'available': on_hand, # Available = on_hand - allocated
'allocated': 0
}
def add_supply(self, location, product_id, quantity, receipt_date):
"""Add future supply (production, purchases, transfers)"""
self.supply_schedule.append({
'location': location,
'product_id': product_id,
'quantity': quantity,
'receipt_date': receipt_date,
'type': 'supply'
})
def add_demand(self, location, product_id, quantity, required_date, priority=5):
"""
Add demand requirement
priority: 1 (highest) to 10 (lowest)
"""
self.demand_schedule.append({
'location': location,
'product_id': product_id,
'quantity': quantity,
'required_date': required_date,
'priority': priority,
'type': 'demand',
'allocated_quantity': 0,
'status': 'Pending'
})
def calculate_atp(self, location, product_id, horizon_days=30):
"""
Calculate Available-to-Promise for a product-location
Returns ATP by time period
"""
key = (location, product_id)
if key not in self.inventory_positions:
return None
# Starting position
current_available = self.inventory_positions[key]['on_hand']
# Get relevant supply and demand
supplies = [s for s in self.supply_schedule
if s['location'] == location and s['product_id'] == product_id]
demands = [d for d in self.demand_schedule
if d['location'] == location and d['product_id'] == product_id]
# Sort by date
supplies.sort(key=lambda x: x['receipt_date'])
demands.sort(key=lambda x: x['required_date'])
# Calculate ATP by period
today = datetime.now().date()
atp_schedule = []
for day_offset in range(horizon_days + 1):
target_date = today + timedelta(days=day_offset)
# Add supplies arriving on or before this date
supplies_today = sum(s['quantity'] for s in supplies
if s['receipt_date'] <= target_date)
# Subtract demands required on or before this date
demands_today = sum(d['quantity'] for d in demands
if d['required_date'] <= target_date)
atp = current_available + supplies_today - demands_today
atp_schedule.append({
'date': target_date,
'day_offset': day_offset,
'atp': max(0, atp), # ATP cannot be negative
'cumulative_supply': supplies_today,
'cumulative_demand': demands_today
})
return pd.DataFrame(atp_schedule)
def allocate_supply(self, allocation_method='priority'):
"""
Allocate available supply to demands
allocation_method: 'priority', 'fcfs', 'pro_rata'
"""
if allocation_method == 'priority':
return self._allocate_by_priority()
elif allocation_method == 'fcfs':
return self._allocate_fcfs()
elif allocation_method == 'pro_rata':
return self._allocate_pro_rata()
else:
raise ValueError(f"Unknown allocation method: {allocation_method}")
def _allocate_by_priority(self):
"""Allocate supply based on demand priority"""
# Sort demands by priority (1 = highest) then by date
sorted_demands = sorted(self.demand_schedule,
key=lambda x: (x['priority'], x['required_date']))
allocations = []
for demand in sorted_demands:
location = demand['location']
product_id = demand['product_id']
quantity_needed = demand['quantity']
key = (location, product_id)
if key not in self.inventory_positions:
continue
available = self.inventory_positions[key]['available']
if available > 0:
# Allocate as much as possible
allocated = min(quantity_needed, available)
# Update available inventory
self.inventory_positions[key]['available'] -= allocated
self.inventory_positions[key]['allocated'] += allocated
# Update demand
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == quantity_needed else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'priority': demand['priority'],
'requested_quantity': quantity_needed,
'allocated_quantity': allocated,
'shortfall': quantity_needed - allocated
})
return pd.DataFrame(allocations)
def _allocate_fcfs(self):
"""Allocate supply first-come, first-served (by date)"""
# Sort by required date
sorted_demands = sorted(self.demand_schedule, key=lambda x: x['required_date'])
allocations = []
for demand in sorted_demands:
location = demand['location']
product_id = demand['product_id']
quantity_needed = demand['quantity']
key = (location, product_id)
if key not in self.inventory_positions:
continue
available = self.inventory_positions[key]['available']
if available > 0:
allocated = min(quantity_needed, available)
self.inventory_positions[key]['available'] -= allocated
self.inventory_positions[key]['allocated'] += allocated
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == quantity_needed else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'requested_quantity': quantity_needed,
'allocated_quantity': allocated,
'shortfall': quantity_needed - allocated
})
return pd.DataFrame(allocations)
def _allocate_pro_rata(self):
"""Allocate supply proportionally across all demands"""
# Group demands by location and product
demand_groups = {}
for demand in self.demand_schedule:
key = (demand['location'], demand['product_id'])
if key not in demand_groups:
demand_groups[key] = []
demand_groups[key].append(demand)
allocations = []
for key, demands in demand_groups.items():
location, product_id = key
if key not in self.inventory_positions:
continue
available = self.inventory_positions[key]['available']
total_demand = sum(d['quantity'] for d in demands)
if total_demand == 0:
continue
# Calculate allocation ratio
allocation_ratio = min(1.0, available / total_demand)
for demand in demands:
allocated = int(demand['quantity'] * allocation_ratio)
demand['allocated_quantity'] = allocated
demand['status'] = 'Fulfilled' if allocated == demand['quantity'] else 'Partial'
allocations.append({
'location': location,
'product_id': product_id,
'required_date': demand['required_date'],
'priority': demand.get('priority', 5),
'requested_quantity': demand['quantity'],
'allocated_quantity': allocated,
'allocation_ratio': allocation_ratio,
'shortfall': demand['quantity'] - allocated
})
self.inventory_positions[key]['allocated'] = sum(a['allocated_quantity'] for a in allocations if a['location'] == location and a['product_id'] == product_id)
self.inventory_positions[key]['available'] = available - self.inventory_positions[key]['allocated']
return pd.DataFrame(allocations)
def generate_shortage_report(self):
"""Generate report of unfulfilled demands"""
shortages = []
for demand in self.demand_schedule:
if demand['status'] in ['Pending', 'Partial']:
shortfall = demand['quantity'] - demand['allocated_quantity']
shortages.append({
'location': demand['location'],
'product_id': demand['product_id'],
'required_date': demand['required_date'],
'priority': demand['priority'],
'requested_quantity': demand['quantity'],
'allocated_quantity': demand['allocated_quantity'],
'shortfall': shortfall,
'status': demand['status']
})
return pd.DataFrame(shortages) if shortages else pd.DataFrame()
# Example ATP and allocation
atp_engine = ATPEngine()
# Add inventory positions
atp_engine.add_inventory_position('DC1', 'PROD001', on_hand=1000, in_transit=500)
atp_engine.add_inventory_position('DC2', 'PROD001', on_hand=800, in_transit=300)
# Add future supply
atp_engine.add_supply('DC1', 'PROD001', 500, datetime.now().date() + timedelta(days=10))
atp_engine.add_supply('DC1', 'PROD001', 700, datetime.now().date() + timedelta(days=20))
# Add demands with priorities
atp_engine.add_demand('DC1', 'PROD001', 600, datetime.now().date() + timedelta(days=5), priority=1) # High priority
atp_engine.add_demand('DC1', 'PROD001', 400, datetime.now().date() + timedelta(days=7), priority=3)
atp_engine.add_demand('DC1', 'PROD001', 500, datetime.now().date() + timedelta(days=12), priority=2)
atp_engine.add_demand('DC1', 'PROD001', 300, datetime.now().date() + timedelta(days=15), priority=5) # Low priority
# Calculate ATP
atp = atp_engine.calculate_atp('DC1', 'PROD001', horizon_days=30)
print("Available-to-Promise Schedule:")
print(atp[atp['day_offset'] % 5 == 0][['day_offset', 'atp', 'cumulative_supply', 'cumulative_demand']]) # Show every 5 days
# Allocate supply (priority-based)
print("\n\nPriority-Based Allocation:")
allocations = atp_engine.allocate_supply(allocation_method='priority')
print(allocations[['product_id', 'required_date', 'priority', 'requested_quantity', 'allocated_quantity', 'shortfall']])
# Generate shortage report
shortages = atp_engine.generate_shortage_report()
print("\n\nShortage Report:")
if not shortages.empty:
print(shortages[['product_id', 'required_date', 'priority', 'shortfall', 'status']])
else:
print("No shortages - all demands fulfilled")
基于优化的分配
多目标分配优化
from scipy.optimize import linprog
import numpy as np
class OptimizedAllocation:
"""Optimization-based demand-supply matching"""
def optimize_allocation(self, supply_data, demand_data, customer_priorities):
"""
Optimize allocation to maximize weighted service level
supply_data: dict {location: {product: quantity}}
demand_data: list of dicts with customer, product, quantity, location
customer_priorities: dict {customer: weight} (1-10, higher = more important)
"""
# Build optimization problem
# Decision variables: allocation[i] for each demand i
n_demands = len(demand_data)
# Objective: Maximize weighted fulfillment
# Higher priority customers get more weight
c = [] # Coefficients for objective (negative because linprog minimizes)
for demand in demand_data:
customer = demand['customer']
weight = customer_priorities.get(customer, 5) # Default weight 5
c.append(-weight * demand['quantity']) # Negative for maximization
c = np.array(c)
# Constraints: Supply constraints
# Sum of allocations for each product-location <= available supply
A_ub = [] # Inequality constraint matrix
b_ub = [] # Inequality constraint bounds
# Group demands by product-location
product_location_demands = {}
for i, demand in enumerate(demand_data):
key = (demand['product'], demand['location'])
if key not in product_location_demands:
product_location_demands[key] = []
product_location_demands[key].append(i)
# Create constraint for each product-location
for (product, location), demand_indices in product_location_demands.items():
constraint_row = np.zeros(n_demands)
for idx in demand_indices:
constraint_row[idx] = 1 # Sum allocations for this product-location
A_ub.append(constraint_row)
# Available supply for this product-location
available_supply = supply_data.get(location, {}).get(product, 0)
b_ub.append(available_supply)
A_ub = np.array(A_ub)
b_ub = np.array(b_ub)
# Bounds: 0 <= allocation[i] <= demand[i]
bounds = [(0, demand['quantity']) for demand in demand_data]
# Solve
result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
if result.success:
allocations = result.x
# Create allocation results
allocation_results = []
for i, demand in enumerate(demand_data):
allocated = round(allocations[i], 2)
fulfillment_rate = (allocated / demand['quantity'] * 100) if demand['quantity'] > 0 else 100
allocation_results.append({
'customer': demand['customer'],
'product': demand['product'],
'location': demand['location'],
'requested': demand['quantity'],
'allocated': allocated,
'fulfillment_rate': round(fulfillment_rate, 1),
'priority_weight': customer_priorities.get(demand['customer'], 5)
})
return {
'success': True,
'allocations': pd.DataFrame(allocation_results),
'objective_value': -result.fun, # Negative because we minimized negative
'solver_status': result.message
}
else:
return {
'success': False,
'message': result.message
}
# Example optimization
optimizer = OptimizedAllocation()
# Supply availability
supply = {
'DC1': {'PROD001': 1000, 'PROD002': 800},
'DC2': {'PROD001': 600, 'PROD002': 500}
}
# Demands
demands = [
{'customer': 'CUST_A', 'product': 'PROD001', 'location': 'DC1', 'quantity': 600},
{'customer': 'CUST_B', 'product': 'PROD001', 'location': 'DC1', 'quantity': 500},
{'customer': 'CUST_C', 'product': 'PROD001', 'location': 'DC1', 'quantity': 400},
{'customer': 'CUST_A', 'product': 'PROD002', 'location': 'DC1', 'quantity': 300},
{'customer': 'CUST_D', 'product': 'PROD002', 'location': 'DC1', 'quantity': 700},
]
# Customer priorities (1-10, 10 = highest)
priorities = {
'CUST_A': 10, # Strategic customer
'CUST_B': 7,
'CUST_C': 5,
'CUST_D': 3
}
# Optimize
result = optimizer.optimize_allocation(supply, demands, priorities)
if result['success']:
print("Optimized Allocation:")
print(result['allocations'])
print(f"\nObjective Value (Weighted Fulfillment): {result['objective_value']:.2f}")
else:
print(f"Optimization failed: {result['message']}")
工具与库
Python 库
优化:
scipy.optimize:线性和非线性优化pulp:线性规划pyomo:优化建模ortools:Google OR-Tools
数据处理:
pandas:数据处理numpy:数值计算
预测集成:
statsmodels:统计模型prophet:预测
商业软件
计划与分配:
- Blue Yonder:分配与履约
- Kinaxis RapidResponse:S&OP 与分配
- o9 Solutions:需求与供应匹配
- SAP IBP:集成业务计划
- Oracle Demantra:需求与供应计划
订单管理:
- Manhattan OMS:订单管理与分配
- Blue Yonder Order Management:履约优化
- Fluent Commerce:订单编排
- Salesforce Order Management:Commerce Cloud
ATP/CTP 引擎:
- SAP ATP:可承诺量
- Oracle ATP:全球订单承诺
- Kinaxis ATP:实时承诺
常见挑战与解决方案
挑战:目标冲突
问题:
- 收入最大化与客户公平性之间的冲突
- 短期利润与长期关系之间的冲突
- 服务水平与成本之间的冲突
- 利益相关方之间的利益冲突
解决方案:
- 采用带权重的多目标优化
- 建立清晰的优先级框架
- 通过情景分析展示权衡取舍
- 通过高管治理机制解决冲突
- 对客户进行分层分类
- 使用平衡计分卡
挑战:动态条件
问题:
- 需求和供应持续变化
- 需要频繁重新优化
- 订单持续到达
- 需要实时更新
解决方案:
- 事件驱动的匹配(在发生变化时触发)
- 定期重新优化周期
- 滚动周期法
- 预留和释放逻辑
- 应对不确定性的缓冲
- 近实时系统
挑战:数据质量
问题:
- 库存位置不准确
- 供应计划过时
- 预测误差
- 系统延迟
解决方案:
- 实时库存可见性
- 自动化数据馈送
- 数据验证与核对
- 应对数据不确定性的缓冲
- 定期循环盘点
- ATP 中的安全余量
挑战:客户沟通
问题:
- 管理客户对分配的预期
- 解释短缺情况
- 透明度与竞争信息之间的权衡
- 对公平性的感知
解决方案:
- 提前传达清晰的分配政策
- 主动通知短缺情况
- 提供替代方案
- 提高历史分配的透明度
- 提供可查看信息的客户门户
- 定期开展业务回顾
挑战:大规模复杂性
问题:
- SKU、地点和客户数量庞大
- 优化所需的计算时间
- 组合爆炸
- 网络约束
解决方案:
- 分层优化(先汇总,再细化)
- 针对大型问题采用启发式方法
- 并行处理
- 增量更新,而非全面重新优化
- 尽可能简化
- 高性能计算
挑战:跨职能协同
问题:
- 销售部门希望最大限度地满足需求
- 财务部门希望减少库存
- 运营部门希望保持稳定
- 激励机制不一致
解决方案:
- 共享 KPI 和目标
- 集成 S&OP 流程
- 跨职能治理
- 透明的决策规则
- 定期召开协调会议
- 高管支持
输出格式
需求与供应匹配报告
执行摘要:
- 整体需求与供应平衡情况
- 已做出的分配决策
- 短缺情况及其影响
- 所需采取的行动
供需平衡:
| 产品 | 总需求 | 总供应 | 差额 | 状态 |
|---|---|---|---|---|
| PROD001 | 2,500 | 2,800 | +300 | ✓ 过剩 |
| PROD002 | 1,800 | 1,500 | -300 | ⚠ 短缺 |
| PROD003 | 1,200 | 1,200 | 0 | ✓ 平衡 |
| PROD004 | 950 | 600 | -350 | ⚠⚠ 严重短缺 |
分配结果:
| 客户 | 产品 | 请求量 | 分配量 | 满足率 % | 优先级 | 状态 |
|---|---|---|---|---|---|---|
| CUST_A | PROD001 | 600 | 600 | 100% | 1 | 已满足 |
| CUST_B | PROD001 | 500 | 500 | 100% | 3 | 已满足 |
| CUST_C | PROD002 | 800 | 600 | 75% | 2 | 部分满足 |
| CUST_D | PROD002 | 500 | 0 | 0% | 5 | 未满足 |
短缺影响:
| 产品 | 短缺数量 | 受影响客户 | 风险收入 | 缓解措施 |
|---|---|---|---|---|
| PROD002 | 300 | 2 | $45K | 加急生产,以 PROD003 替代 |
| PROD004 | 350 | 3 | $28K | 仅分配给战略客户 |
ATP 时段:
| 产品 | 地点 | 今天 | 第 1 周 | 第 2 周 | 第 3 周 | 第 4 周 |
|---|---|---|---|---|---|---|
| PROD001 | DC1 | 400 | 650 | 1,100 | 1,300 | 1,200 |
| PROD002 | DC1 | 0 | 200 | 600 | 800 | 750 |
行动事项:
| 优先级 | 行动 | 负责人 | 截止日期 | 状态 |
|---|---|---|---|---|
| 紧急 | 加急生产 PROD002 | 运营 | 明天 | 进行中 |
| 高 | 向受影响客户说明短缺情况 | 销售 | 今天 | 待处理 |
| 中 | 评估 PROD004 的替代产品 | 产品管理 | 本周 | 计划中 |
需要询问的问题
如果需要更多背景信息:
- 当前的供需平衡情况如何?
- 倾向采用哪种分配方式?(优先级、按比例分配、优化)
- 客户细分或优先级划分情况如何?
- 服务水平目标是什么?
- 存在哪些供应约束?(产能、库存、物流)
- 需要多频繁地进行匹配?
- 哪些系统提供需求和供应数据?
- 短缺与过剩的成本分别是多少?
- 是否有可替代产品?
- 分配决策权限如何划分?
相关技能
- demand-forecasting:用于提供准确的需求数据
- sales-operations-planning:用于制定综合供需计划
- inventory-optimization:用于确定安全库存和缓冲库存
- capacity-planning:用于应对供应产能约束
- supplier-collaboration:用于协同供应计划
- control-tower-design:用于实时可视化和监控
- network-design:用于优化履约网络
- route-optimization:用于处理匹配中的物流约束
评论
支持 Markdown