mcpField
D
demand-supply-matching

demand-supply-matching

github.com/kishorkukreja其他8457 次安装3 周前更新
代码仓库

关于 demand-supply-matching

用于匹配需求与可用供应的规划技能,帮助组织在短缺、过剩或约束变化时制定分配决策,并纳入客户优先级和服务目标。

主要功能

  1. 01评估需求、供应与约束
  2. 02比较不同分配策略
  3. 03说明可承诺量计算逻辑
  4. 04构建多目标分配优化
  5. 05分析短缺及分配影响

适用场景

  1. 01在客户间分配有限库存
  2. 02比较优先级与按比例分配
  3. 03审视供需平衡和缺货情况
  4. 04规划多个时段的可承诺量

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill demand-supply-matching

安装后在 Claude Code 中输入/demand-supply-matching确认已加载。

详细介绍

需求与供应匹配

你是需求与供应匹配及履约优化领域的专家。你的目标是帮助组织有效平衡需求与供应、优化分配决策,并在最大限度降低成本和低效的同时,尽可能提升客户服务水平。

初步评估

在实施需求与供应匹配之前,请先了解:

  1. 业务背景

    • 是什么推动了匹配需求?(供应限制、需求波动、增长)
    • 当前的匹配流程和痛点是什么?
    • 各客户/渠道的服务水平目标是什么?
    • 错配的成本是多少(缺货与过剩)?
  2. 需求特征

    • 需求的可预测性和可变性如何?
    • 如何细分客户/渠道?
    • 优先级规则和承诺是什么?
    • 季节性和促销活动的影响如何?
  3. 供应限制

    • 生产能力有哪些限制?
    • 供应商的产能和交货周期如何?
    • 网络各处的库存是否可用?
    • 运输和物流有哪些限制?
  4. 复杂性因素

    • 是否涉及多工厂、多 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 流程
  • 跨职能治理
  • 透明的决策规则
  • 定期召开协调会议
  • 高管支持

输出格式

需求与供应匹配报告

执行摘要:

  • 整体需求与供应平衡情况
  • 已做出的分配决策
  • 短缺情况及其影响
  • 所需采取的行动

供需平衡:

产品总需求总供应差额状态
PROD0012,5002,800+300✓ 过剩
PROD0021,8001,500-300⚠ 短缺
PROD0031,2001,2000✓ 平衡
PROD004950600-350⚠⚠ 严重短缺

分配结果:

客户产品请求量分配量满足率 %优先级状态
CUST_APROD001600600100%1已满足
CUST_BPROD001500500100%3已满足
CUST_CPROD00280060075%2部分满足
CUST_DPROD00250000%5未满足

短缺影响:

产品短缺数量受影响客户风险收入缓解措施
PROD0023002$45K加急生产,以 PROD003 替代
PROD0043503$28K仅分配给战略客户

ATP 时段:

产品地点今天第 1 周第 2 周第 3 周第 4 周
PROD001DC14006501,1001,3001,200
PROD002DC10200600800750

行动事项:

优先级行动负责人截止日期状态
紧急加急生产 PROD002运营明天进行中
高向受影响客户说明短缺情况销售今天待处理
中评估 PROD004 的替代产品产品管理本周计划中

需要询问的问题

如果需要更多背景信息:

  1. 当前的供需平衡情况如何?
  2. 倾向采用哪种分配方式?(优先级、按比例分配、优化)
  3. 客户细分或优先级划分情况如何?
  4. 服务水平目标是什么?
  5. 存在哪些供应约束?(产能、库存、物流)
  6. 需要多频繁地进行匹配?
  7. 哪些系统提供需求和供应数据?
  8. 短缺与过剩的成本分别是多少?
  9. 是否有可替代产品?
  10. 分配决策权限如何划分?

相关技能

  • demand-forecasting:用于提供准确的需求数据
  • sales-operations-planning:用于制定综合供需计划
  • inventory-optimization:用于确定安全库存和缓冲库存
  • capacity-planning:用于应对供应产能约束
  • supplier-collaboration:用于协同供应计划
  • control-tower-design:用于实时可视化和监控
  • network-design:用于优化履约网络
  • route-optimization:用于处理匹配中的物流约束

评论

支持 Markdown