mcpField
P
procurement-optimization

procurement-optimization

github.com/kishorkukrejaその他84167 回インストール3 週間前に更新
リポジトリ

procurement-optimization について

購入コスト、サービス要件、供給リスクのバランスを取るための調達最適化スキルです。EOQやロットサイズ計画などを用いて、発注量、仕入先配分、時期、契約を検討します。

主な機能

  1. 01EOQと発注量の分析
  2. 02数量割引の評価
  3. 03複数仕入先への配分
  4. 04発注時期とロットサイズ計画
  5. 05供給リスクの評価
  6. 06コストとリスクの比較分析

活用シーン

  1. 01仕入先ごとの発注配分の比較
  2. 02需要変動を考慮した補充計画
  3. 03割引と在庫保有コストの評価
  4. 04主仕入先と予備仕入先の配分調整

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/procurement-optimizationを入力して読み込まれたか確認します。

詳細

調達最適化

調達最適化と意思決定科学の専門家として、サービスレベル、容量制約、リスク管理の要件を満たしながら、総コストを最小限に抑える最適な購買判断を組織が行えるよう支援します。

初期評価

調達を最適化する前に、次の点を把握します。

  1. 調達の背景

    • どのような製品/資材を調達していますか?
    • 現在の調達プロセスと課題は何ですか?
    • 支出額と調達頻度はどのくらいですか?
    • サプライヤーの数と現在の割り当てはどうなっていますか?
  2. ビジネス目標

    • 主な目標は何ですか?(コスト、サービス、リスク、持続可能性)
    • コスト項目は何ですか?(価格、運賃、関税、保管費)
    • サービスレベルの要件は何ですか?
    • リスク許容度はどの程度ですか?
  3. 制約

    • サプライヤーの供給能力に上限はありますか?
    • 最小発注数量(MOQ)はありますか?
    • リードタイムと納入可能期間はどのくらいですか?
    • 予算または運転資本の制約はありますか?
    • 品質または認証の要件はありますか?
  4. データの利用可能性

    • 過去の需要と発注パターンはありますか?
    • サプライヤーの価格(数量割引を含む)はありますか?
    • 在庫保管コストは把握していますか?
    • 発注処理コストは把握していますか?
    • 輸送費と物流コストは把握していますか?

調達最適化フレームワーク

主な意思決定領域

1. 発注数量の決定

  • 経済的発注量(EOQ)
  • 制約付きロットサイズ決定
  • 数量割引の最適化
  • 共同補充

2. サプライヤー割り当ての決定

  • 単一調達と複数調達の比較
  • サプライヤー間での発注分割
  • ポートフォリオ最適化
  • サプライヤーの多様化

3. タイミングの決定

  • 発注点
  • 発注スケジュール
  • リードタイム管理
  • 安全在庫水準

4. 契約の決定

  • 固定数量と柔軟な数量の比較
  • 価格と数量コミットメントの比較
  • 長期購入とスポット購入の比較
  • オプションとヘッジ

経済的発注量(EOQ)

従来のEOQモデル

前提条件:

  • 需要率が一定
  • 補充が瞬時に行われる
  • 欠品が発生しない
  • 発注コストと保管コストが固定

数式:

EOQ = √(2 × D × S / H)

Where:
D = Annual demand (units)
S = Fixed ordering cost per order
H = Annual holding cost per unit
import numpy as np
import matplotlib.pyplot as plt

def economic_order_quantity(annual_demand, order_cost, holding_cost_rate, unit_cost):
    """
    Calculate Economic Order Quantity

    Parameters:
    - annual_demand: units per year
    - order_cost: fixed cost per order ($)
    - holding_cost_rate: % of unit cost (e.g., 0.25 for 25%)
    - unit_cost: cost per unit ($)

    Returns:
    - EOQ, total annual cost, number of orders
    """

    holding_cost_per_unit = unit_cost * holding_cost_rate

    # EOQ formula
    eoq = np.sqrt((2 * annual_demand * order_cost) / holding_cost_per_unit)

    # Number of orders per year
    num_orders = annual_demand / eoq

    # Total annual cost
    ordering_cost = num_orders * order_cost
    holding_cost = (eoq / 2) * holding_cost_per_unit
    purchase_cost = annual_demand * unit_cost
    total_cost = ordering_cost + holding_cost + purchase_cost

    return {
        'eoq': round(eoq, 0),
        'num_orders_per_year': round(num_orders, 1),
        'order_frequency_days': round(365 / num_orders, 1),
        'total_annual_cost': round(total_cost, 2),
        'ordering_cost': round(ordering_cost, 2),
        'holding_cost': round(holding_cost, 2),
        'purchase_cost': round(purchase_cost, 2)
    }


# Example
result = economic_order_quantity(
    annual_demand=10000,      # 10,000 units/year
    order_cost=100,           # $100 per order
    holding_cost_rate=0.25,   # 25% carrying cost
    unit_cost=50              # $50/unit
)

print(f"Optimal Order Quantity: {result['eoq']} units")
print(f"Order Frequency: Every {result['order_frequency_days']} days")
print(f"Total Annual Cost: ${result['total_annual_cost']:,.0f}")
数量割引を考慮したEOQ

全数量割引:

  • 一定の数量で価格が変わる
  • 購入した全数量に割引価格が適用される
def eoq_quantity_discounts(annual_demand, order_cost, holding_cost_rate, price_breaks):
    """
    EOQ with all-units quantity discounts

    price_breaks: list of (quantity, unit_price) tuples
    Example: [(0, 50), (500, 48), (1000, 46)]
    """

    price_breaks = sorted(price_breaks, key=lambda x: x[0])

    best_option = None
    best_cost = float('inf')

    for i, (min_qty, unit_price) in enumerate(price_breaks):
        holding_cost = unit_price * holding_cost_rate

        # Calculate EOQ for this price
        eoq = np.sqrt((2 * annual_demand * order_cost) / holding_cost)

        # Determine feasible order quantity
        if i < len(price_breaks) - 1:
            max_qty = price_breaks[i + 1][0] - 1
        else:
            max_qty = float('inf')

        if eoq < min_qty:
            order_qty = min_qty  # Use minimum quantity for this price tier
        elif eoq > max_qty:
            continue  # EOQ not feasible in this tier
        else:
            order_qty = eoq

        # Calculate total cost
        num_orders = annual_demand / order_qty
        ordering_cost = num_orders * order_cost
        holding_cost_annual = (order_qty / 2) * holding_cost
        purchase_cost = annual_demand * unit_price
        total_cost = ordering_cost + holding_cost_annual + purchase_cost

        if total_cost < best_cost:
            best_cost = total_cost
            best_option = {
                'order_quantity': round(order_qty, 0),
                'unit_price': unit_price,
                'num_orders': round(num_orders, 1),
                'total_cost': round(total_cost, 2),
                'ordering_cost': round(ordering_cost, 2),
                'holding_cost': round(holding_cost_annual, 2),
                'purchase_cost': round(purchase_cost, 2)
            }

    return best_option


# Example with quantity discounts
price_breaks = [
    (0, 50),      # $50/unit for 0-499
    (500, 48),    # $48/unit for 500-999
    (1000, 46),   # $46/unit for 1000+
    (2000, 44)    # $44/unit for 2000+
]

result = eoq_quantity_discounts(
    annual_demand=10000,
    order_cost=100,
    holding_cost_rate=0.25,
    price_breaks=price_breaks
)

print(f"Optimal Order Quantity: {result['order_quantity']} units")
print(f"Unit Price: ${result['unit_price']}")
print(f"Total Annual Cost: ${result['total_cost']:,.0f}")
print(f"  Purchase: ${result['purchase_cost']:,.0f}")
print(f"  Ordering: ${result['ordering_cost']:,.0f}")
print(f"  Holding: ${result['holding_cost']:,.0f}")

サプライヤー割り当ての最適化

複数調達の問題

目的: 容量、品質、リスクの制約を満たしながら、複数のサプライヤーに発注を割り当て、総コストを最小化します。

数理モデル:

Decision Variables:
  x_i = quantity ordered from supplier i

Objective:
  Minimize: Σ (p_i × x_i + f_i × y_i + t_i × x_i)

Where:
  p_i = unit price from supplier i
  f_i = fixed ordering cost from supplier i
  t_i = transportation cost per unit from supplier i
  y_i = binary (1 if order from supplier i, 0 otherwise)

Constraints:
  Σ x_i >= D                    (meet demand)
  x_i <= C_i × y_i              (supplier capacity)
  x_i >= MOQ_i × y_i            (minimum order quantity)
  Σ (q_i × x_i) / Σ x_i >= Q   (average quality requirement)
  x_i / Σ x_i <= R_max          (diversification - max % per supplier)
from pulp import *
import pandas as pd

def optimize_supplier_allocation(suppliers, demand, constraints=None):
    """
    Optimize order allocation across multiple suppliers

    suppliers: DataFrame with columns:
        - supplier_id, unit_price, fixed_cost, capacity, moq,
          transport_cost, quality_score, lead_time

    demand: total quantity needed

    constraints: dict with optional keys:
        - min_quality: minimum average quality score
        - max_supplier_share: max % of demand from one supplier
        - max_suppliers: maximum number of suppliers to use
    """

    if constraints is None:
        constraints = {}

    # Create problem
    prob = LpProblem("Supplier_Allocation", LpMinimize)

    # Decision variables
    # x[i] = quantity from supplier i
    x = LpVariable.dicts("Quantity",
                        suppliers.index,
                        lowBound=0,
                        cat='Continuous')

    # y[i] = 1 if order from supplier i, 0 otherwise
    y = LpVariable.dicts("Use",
                        suppliers.index,
                        cat='Binary')

    # Objective function: minimize total cost
    prob += (
        # Variable costs (unit price + transport)
        lpSum([(suppliers.loc[i, 'unit_price'] +
                suppliers.loc[i, 'transport_cost']) * x[i]
               for i in suppliers.index]) +

        # Fixed ordering costs
        lpSum([suppliers.loc[i, 'fixed_cost'] * y[i]
               for i in suppliers.index])
    )

    # Constraint 1: Meet total demand
    prob += lpSum([x[i] for i in suppliers.index]) >= demand, "Meet_Demand"

    # Constraint 2: Capacity limits
    for i in suppliers.index:
        prob += x[i] <= suppliers.loc[i, 'capacity'] * y[i], f"Capacity_{i}"

    # Constraint 3: Minimum order quantities
    for i in suppliers.index:
        prob += x[i] >= suppliers.loc[i, 'moq'] * y[i], f"MOQ_{i}"

    # Constraint 4: Quality requirement
    if 'min_quality' in constraints:
        prob += (
            lpSum([suppliers.loc[i, 'quality_score'] * x[i]
                   for i in suppliers.index]) >=
            constraints['min_quality'] * demand,
            "Min_Quality"
        )

    # Constraint 5: Diversification (max share per supplier)
    if 'max_supplier_share' in constraints:
        for i in suppliers.index:
            prob += (
                x[i] <= constraints['max_supplier_share'] * demand,
                f"Max_Share_{i}"
            )

    # Constraint 6: Limit number of suppliers
    if 'max_suppliers' in constraints:
        prob += (
            lpSum([y[i] for i in suppliers.index]) <=
            constraints['max_suppliers'],
            "Max_Suppliers"
        )

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    # Extract results
    if LpStatus[prob.status] != 'Optimal':
        return {'status': LpStatus[prob.status], 'solution': None}

    results = []
    for i in suppliers.index:
        if x[i].varValue > 0.01:
            qty = x[i].varValue
            unit_cost = (suppliers.loc[i, 'unit_price'] +
                        suppliers.loc[i, 'transport_cost'])
            variable_cost = qty * unit_cost
            fixed_cost = suppliers.loc[i, 'fixed_cost']
            total_cost = variable_cost + fixed_cost

            results.append({
                'Supplier': suppliers.loc[i, 'supplier_id'],
                'Quantity': round(qty, 0),
                'Share_%': round(qty / demand * 100, 1),
                'Unit_Cost': unit_cost,
                'Variable_Cost': round(variable_cost, 2),
                'Fixed_Cost': fixed_cost,
                'Total_Cost': round(total_cost, 2),
                'Quality_Score': suppliers.loc[i, 'quality_score'],
                'Lead_Time': suppliers.loc[i, 'lead_time']
            })

    results_df = pd.DataFrame(results)
    results_df = results_df.sort_values('Total_Cost')

    return {
        'status': 'Optimal',
        'total_cost': round(value(prob.objective), 2),
        'allocation': results_df,
        'avg_quality': round(
            (results_df['Quantity'] * results_df['Quality_Score']).sum() /
            results_df['Quantity'].sum(), 2
        ),
        'num_suppliers': len(results_df)
    }


# Example usage
suppliers_data = pd.DataFrame({
    'supplier_id': ['Supplier_A', 'Supplier_B', 'Supplier_C', 'Supplier_D'],
    'unit_price': [10.0, 9.5, 10.5, 9.8],
    'fixed_cost': [500, 400, 600, 450],
    'transport_cost': [1.0, 1.5, 0.8, 1.2],
    'capacity': [5000, 4000, 6000, 3000],
    'moq': [500, 300, 400, 200],
    'quality_score': [9, 8, 10, 8.5],  # 0-10 scale
    'lead_time': [21, 28, 14, 21]  # days
})

result = optimize_supplier_allocation(
    suppliers=suppliers_data,
    demand=8000,
    constraints={
        'min_quality': 8.5,        # Minimum average quality
        'max_supplier_share': 0.6, # Max 60% from one supplier
        'max_suppliers': 3         # Use at most 3 suppliers
    }
)

print(f"Status: {result['status']}")
print(f"Total Cost: ${result['total_cost']:,.2f}")
print(f"Number of Suppliers: {result['num_suppliers']}")
print(f"Average Quality: {result['avg_quality']}/10")
print("\nAllocation:")
print(result['allocation'])
ポートフォリオ最適化のアプローチ

効率的フロンティア: コストとリスク(サプライヤーの多様化)のトレードオフ

import numpy as np
from scipy.optimize import minimize

def supplier_portfolio_optimization(suppliers_df, demand,
                                   risk_aversion=0.5):
    """
    Optimize supplier portfolio considering cost and risk

    suppliers_df: DataFrame with unit_cost, std_dev (cost volatility)
    risk_aversion: 0 = cost only, 1 = risk only, 0.5 = balanced
    """

    n_suppliers = len(suppliers_df)

    def objective(weights):
        """Minimize weighted combination of cost and risk"""

        # Expected total cost
        expected_cost = np.sum(
            weights * suppliers_df['unit_cost'].values * demand
        )

        # Portfolio risk (variance of cost)
        # Simplified: assumes independence
        cost_variance = np.sum(
            (weights * demand) ** 2 * suppliers_df['std_dev'].values ** 2
        )
        cost_risk = np.sqrt(cost_variance)

        # Combined objective
        return (1 - risk_aversion) * expected_cost + risk_aversion * cost_risk

    # Constraints
    constraints = [
        {'type': 'eq', 'fun': lambda w: np.sum(w) - 1},  # Weights sum to 1
    ]

    # Bounds: each weight between 0 and max_share
    bounds = [(0, 0.6) for _ in range(n_suppliers)]

    # Initial guess: equal weights
    x0 = np.ones(n_suppliers) / n_suppliers

    # Optimize
    result = minimize(objective, x0, method='SLSQP',
                     bounds=bounds, constraints=constraints)

    if result.success:
        weights = result.x
        allocation = weights * demand

        return {
            'weights': weights,
            'allocation': allocation,
            'expected_cost': np.sum(weights * suppliers_df['unit_cost'].values * demand),
            'cost_std': np.sqrt(np.sum((weights * demand) ** 2 *
                                      suppliers_df['std_dev'].values ** 2))
        }
    else:
        return None

高度な調達モデル

共同補充問題(JRP)

同一サプライヤーから複数品目を調達:

  • 固定発注コストを共有
  • 発注タイミングを調整
  • 総コストを最小化
def joint_replenishment_problem(items_df, shared_fixed_cost):
    """
    Joint replenishment for multiple items

    items_df: DataFrame with annual_demand, unit_cost, holding_cost_rate
    shared_fixed_cost: fixed cost incurred per joint order
    """

    # Calculate individual EOQs
    items_df['individual_eoq'] = np.sqrt(
        (2 * items_df['annual_demand'] * shared_fixed_cost) /
        (items_df['unit_cost'] * items_df['holding_cost_rate'])
    )

    # Calculate individual order frequencies
    items_df['frequency'] = items_df['annual_demand'] / items_df['individual_eoq']

    # Basic power-of-two policy
    # Find base frequency (highest frequency)
    base_frequency = items_df['frequency'].max()

    # Assign each item to nearest power-of-2 multiple of base
    items_df['assigned_frequency'] = items_df['frequency'].apply(
        lambda f: base_frequency / (2 ** round(np.log2(base_frequency / f)))
    )

    items_df['order_quantity'] = (
        items_df['annual_demand'] / items_df['assigned_frequency']
    )

    # Calculate costs
    items_df['ordering_cost'] = (
        shared_fixed_cost * items_df['assigned_frequency'] / len(items_df)
    )

    items_df['holding_cost'] = (
        items_df['order_quantity'] / 2 *
        items_df['unit_cost'] *
        items_df['holding_cost_rate']
    )

    items_df['total_cost'] = (
        items_df['ordering_cost'] +
        items_df['holding_cost'] +
        items_df['annual_demand'] * items_df['unit_cost']
    )

    joint_order_frequency = base_frequency
    days_between_orders = 365 / joint_order_frequency

    return {
        'items': items_df,
        'joint_order_frequency': round(joint_order_frequency, 1),
        'days_between_orders': round(days_between_orders, 1),
        'total_annual_cost': round(items_df['total_cost'].sum(), 2)
    }


# Example: 3 items ordered together
items = pd.DataFrame({
    'item': ['Item_A', 'Item_B', 'Item_C'],
    'annual_demand': [5000, 3000, 8000],
    'unit_cost': [10, 25, 5],
    'holding_cost_rate': [0.25, 0.25, 0.25]
})

result = joint_replenishment_problem(items, shared_fixed_cost=200)

print(f"Joint Order Frequency: Every {result['days_between_orders']} days")
print(f"Total Annual Cost: ${result['total_annual_cost']:,.0f}")
print("\nItem Details:")
print(result['items'][['item', 'order_quantity', 'assigned_frequency']])
動的ロットサイズ決定(Wagner-Whitin)

期間ごとに需要が変動する場合:

  • 需要が期間ごとに変化する
  • バックオーダーがない
  • 計画期間全体の総コストを最小化
def wagner_whitin(demands, setup_cost, holding_cost_per_unit):
    """
    Wagner-Whitin algorithm for dynamic lot sizing

    demands: list of demands by period [d1, d2, d3, ...]
    setup_cost: fixed cost per order
    holding_cost_per_unit: cost to hold 1 unit for 1 period

    Returns: optimal order quantities and total cost
    """

    n_periods = len(demands)

    # DP arrays
    cost = [float('inf')] * (n_periods + 1)
    cost[0] = 0
    order_in = [0] * (n_periods + 1)

    # Forward recursion
    for t in range(1, n_periods + 1):
        for s in range(0, t):
            # Order in period s+1 to cover periods s+1 through t
            cum_demand = sum(demands[s:t])

            # Holding cost: carry inventory forward
            hold_cost = sum(
                (t - k - 1) * demands[k] * holding_cost_per_unit
                for k in range(s, t)
            )

            total_cost = cost[s] + setup_cost + hold_cost

            if total_cost < cost[t]:
                cost[t] = total_cost
                order_in[t] = s + 1

    # Backtrack to find order quantities
    orders = [0] * n_periods
    period = n_periods

    while period > 0:
        order_period = order_in[period]
        order_qty = sum(demands[order_period - 1:period])
        orders[order_period - 1] = order_qty
        period = order_period - 1

    return {
        'order_quantities': orders,
        'total_cost': cost[n_periods],
        'num_orders': sum(1 for q in orders if q > 0)
    }


# Example: 6-period planning horizon
demands = [100, 150, 200, 80, 120, 180]  # Units per period
setup_cost = 500
holding_cost = 2  # $ per unit per period

result = wagner_whitin(demands, setup_cost, holding_cost)

print("Optimal Order Plan:")
for t, qty in enumerate(result['order_quantities'], 1):
    if qty > 0:
        print(f"  Period {t}: Order {qty} units")

print(f"\nTotal Cost: ${result['total_cost']:,.2f}")
print(f"Number of Orders: {result['num_orders']}")

調達リスク管理

供給リスク指標
def calculate_supply_risk_score(supplier_data):
    """
    Calculate comprehensive supply risk score

    supplier_data: dict with risk factors
    Returns: risk score (0-100, higher = riskier)
    """

    risk_score = 0
    factors = []

    # Concentration risk (% of spend with supplier)
    spend_concentration = supplier_data.get('spend_share', 0)
    if spend_concentration > 0.5:
        risk_score += 25
        factors.append("High spend concentration")
    elif spend_concentration > 0.3:
        risk_score += 15
        factors.append("Moderate spend concentration")

    # Geographic risk
    if supplier_data.get('single_location', False):
        risk_score += 15
        factors.append("Single location risk")

    if supplier_data.get('geopolitical_risk', False):
        risk_score += 20
        factors.append("Geopolitical risk")

    # Financial health (0-10 scale, 10 = best)
    financial_score = supplier_data.get('financial_health', 7)
    if financial_score < 5:
        risk_score += 20
        factors.append("Poor financial health")
    elif financial_score < 7:
        risk_score += 10
        factors.append("Moderate financial concerns")

    # Capacity utilization
    capacity_util = supplier_data.get('capacity_utilization', 0.7)
    if capacity_util > 0.95:
        risk_score += 15
        factors.append("Very high capacity utilization")
    elif capacity_util > 0.85:
        risk_score += 8
        factors.append("High capacity utilization")

    # Quality issues (defect rate)
    defect_rate = supplier_data.get('defect_rate_ppm', 0)
    if defect_rate > 1000:
        risk_score += 15
        factors.append("Quality issues")
    elif defect_rate > 500:
        risk_score += 8

    # Delivery performance
    otd_rate = supplier_data.get('on_time_delivery', 1.0)
    if otd_rate < 0.90:
        risk_score += 15
        factors.append("Poor delivery performance")
    elif otd_rate < 0.95:
        risk_score += 8

    risk_level = 'Low' if risk_score < 30 else 'Medium' if risk_score < 60 else 'High'

    return {
        'risk_score': risk_score,
        'risk_level': risk_level,
        'risk_factors': factors
    }
最適なデュアルソーシング

コストとリスクのバランス:

def optimal_dual_sourcing(primary_supplier, backup_supplier,
                         annual_demand, disruption_prob, disruption_cost):
    """
    Determine optimal split between primary and backup supplier

    Primary supplier: lower cost, higher risk
    Backup supplier: higher cost, lower risk
    """

    best_split = None
    best_expected_cost = float('inf')

    # Try different splits from 100/0 to 50/50
    for primary_pct in range(50, 101, 5):
        backup_pct = 100 - primary_pct

        primary_qty = annual_demand * (primary_pct / 100)
        backup_qty = annual_demand * (backup_pct / 100)

        # Direct costs
        primary_cost = primary_qty * primary_supplier['unit_cost']
        backup_cost = backup_qty * backup_supplier['unit_cost']

        # Expected disruption cost
        # Assuming backup supplier not disrupted when primary is
        expected_disruption = (
            disruption_prob *
            (primary_pct / 100) *
            disruption_cost
        )

        total_expected_cost = primary_cost + backup_cost + expected_disruption

        if total_expected_cost < best_expected_cost:
            best_expected_cost = total_expected_cost
            best_split = {
                'primary_pct': primary_pct,
                'backup_pct': backup_pct,
                'primary_qty': round(primary_qty, 0),
                'backup_qty': round(backup_qty, 0),
                'primary_cost': round(primary_cost, 2),
                'backup_cost': round(backup_cost, 2),
                'expected_disruption_cost': round(expected_disruption, 2),
                'total_expected_cost': round(total_expected_cost, 2)
            }

    return best_split


# Example
primary = {'unit_cost': 10.0}
backup = {'unit_cost': 11.5}

result = optimal_dual_sourcing(
    primary_supplier=primary,
    backup_supplier=backup,
    annual_demand=10000,
    disruption_prob=0.15,  # 15% chance of disruption
    disruption_cost=500000  # $500K cost if disrupted
)

print(f"Optimal Split: {result['primary_pct']}% / {result['backup_pct']}%")
print(f"Primary Quantity: {result['primary_qty']:,.0f} units")
print(f"Backup Quantity: {result['backup_qty']:,.0f} units")
print(f"Total Expected Cost: ${result['total_expected_cost']:,.2f}")

ツールとライブラリ

Pythonライブラリ

最適化:

  • pulp:線形計画法(サプライヤー割り当て、ロットサイズ決定)
  • scipy.optimize:一般的な最適化(ポートフォリオ、デュアルソーシング)
  • pyomo:高度な最適化モデリング
  • cvxpy:凸最適化
  • ortools:Google OR-Tools(制約プログラミング)

データ分析:

  • pandas:データ操作
  • numpy:数値計算
  • statsmodels:統計分析

可視化:

  • matplotlib、seaborn:チャートとグラフ
  • plotly:インタラクティブなダッシュボード
商用ソフトウェア

調達の最適化:

  • SAP Ariba:戦略的ソーシングと調達
  • Coupa:ソース・トゥ・ペイのプラットフォーム
  • Jaggaer:戦略的ソーシングスイート
  • GEP SMART:統合型調達
  • PROS:価格と利益の最適化
  • Keelvar:ソーシングの最適化

サプライチェーンの最適化:

  • LLamasoft:サプライチェーンの設計と最適化
  • Blue Yonder:サプライチェーン計画
  • o9 Solutions:統合計画
  • Kinaxis RapidResponse:S&OP プラットフォーム

分析:

  • Tableau、Power BI:調達ダッシュボード
  • SpendHQ:支出分析
  • Zycus:支出分析

よくある課題と解決策

課題:数量割引の複雑さ

問題:

  • 複数の価格区分
  • サプライヤーごとに異なる割引体系
  • 同条件での比較が困難

解決策:

  • 最適化を使用して、すべての組み合わせを評価する
  • 保有コストを含む総着地コストを計算する
  • 需要の不確実性に対する感度分析を行う
  • 大口注文がキャッシュフローに与える影響を考慮する
課題:最小注文数量(MOQ)

問題:

  • MOQ によって過剰在庫が発生する
  • 最適でないサプライヤーの利用を強いられる可能性がある
  • JIT の目標と矛盾する

解決策:

  • 数量の確約と引き換えに MOQ の引き下げを交渉する
  • 他の事業部門と共同で注文する
  • 類似品目を統合する
  • 保管コストを含む総コストを評価する
  • 委託製造業者または販売業者を利用する
課題:リードタイムの変動性

問題:

  • 納期が不確実
  • 安全在庫の必要量に影響する
  • 発注時期の判断が複雑になる

解決策:

  • リードタイムを確率分布としてモデル化する
  • 不確実性を考慮して発注点を最適化する
  • 地域ごとにサプライヤーを分散する
  • ベンダー管理在庫(VMI)を導入する
  • 追跡・可視化ツールを利用する
課題:複数の目的間のトレードオフ

問題:

  • 目標が相反する(コスト、リスク、品質、持続可能性)
  • 関係者によって優先事項が異なる
  • 一部の目標を定量化するのが困難

解決策:

  • 多基準意思決定分析(重み付けスコアリング)
  • パレート最適化(効率的フロンティア)
  • トレードオフを示すシナリオ分析
  • 優先事項をすり合わせるための関係者ワークショップ
  • 副次的な目標に制約を設定する
課題:需要の不確実性

問題:

  • 予測誤差によって過剰発注や発注不足が発生する
  • 最適な注文数量は需要に応じて変わる
  • 陳腐化や在庫切れのリスクがある

解決策:

  • EOQ の計算に予想需要を使用する
  • 安全在庫を最適化する
  • 柔軟な契約(オプション、延期)を利用する
  • ベンダー管理在庫(VMI)を利用する
  • 定期的に見直し、調整する
  • 延期によってリスクをプールする

出力形式

調達最適化レポート

エグゼクティブサマリー:

  • 推奨する調達戦略
  • 総コストと削減の機会
  • 現在のアプローチからの主な変更点
  • 導入要件

最適な発注配分:

サプライヤー配分割合 %単価合計コスト品質リードタイムリスクレベル
Supplier B4,800 units60%$11.00$52,8009/1021 daysLow
Supplier C2,400 units30%$11.30$27,12010/1014 daysLow
Supplier D800 units10%$11.00$8,8008.5/1021 daysMedium
合計8,000 units100%$11.09$88,7209.2/1019 daysLow

コスト内訳:

項目現在最適化後削減額変化率 %
購入価格$95,000$88,000$7,000-7.4%
輸送費$12,000$10,400$1,600-13.3%
発注コスト$2,400$1,350$1,050-43.8%
在庫保有コスト$18,000$15,500$2,500-13.9%
合計$127,400$115,250$12,150-9.5%

発注スケジュール:

Recommended Order Plan (Next 12 Months):

Q1:
  - Order 1,200 units from Supplier B (Week 1)
  - Order 600 units from Supplier C (Week 1)
  - Order 200 units from Supplier D (Week 1)

Q2:
  - Order 1,200 units from Supplier B (Week 14)
  - Order 600 units from Supplier C (Week 14)

Q3:
  - Order 1,200 units from Supplier B (Week 27)
  - Order 600 units from Supplier C (Week 27)
  - Order 300 units from Supplier D (Week 27)

Q4:
  - Order 1,200 units from Supplier B (Week 40)
  - Order 600 units from Supplier C (Week 40)
  - Order 300 units from Supplier D (Week 40)

リスク評価:

  • 全体的な供給リスク:低
  • 取引量の60%を超える単一サプライヤーなし(分散化済み)
  • サプライヤーの平均財務健全性:8.5/10(良好)
  • 地理的分散:3地域
  • 品質実績:不良品率0.9%(優秀)

推奨事項:

  1. 3社のサプライヤーに60/30/10の割合で移行します
  2. 発注コストと在庫保有コストのバランスを取るため、四半期ごとの発注を導入します
  3. 価格を安定させるため、数量コミットメントを含む2年間の契約を交渉します
  4. パフォーマンスKPIと四半期レビューを設定します
  5. 緊急時に備えて、認定済みのバックアップサプライヤー(Supplier A)を確保します

導入計画:

  • 1か月目:選定したサプライヤーとの契約を確定します
  • 2か月目:初回発注を行い、品質を検証します
  • 3か月目:本番の生産量まで段階的に引き上げます
  • 4か月目以降:パフォーマンスを監視し、必要に応じて調整します

質問事項

さらに詳しい情報が必要な場合:

  1. 調達する製品・資材は何ですか?
  2. 年間需要量とその変動幅はどのくらいですか?
  3. 利用可能なサプライヤーは何社あり、それぞれどのような能力を持っていますか?
  4. 主なコスト要因は何ですか?(単価、輸送費、在庫保有費)
  5. 制約はありますか?(最小発注数量、容量制限、品質要件)
  6. 現在の調達方法と課題は何ですか?
  7. 最も重視するのは、最低コスト、リスク軽減、品質のどれですか?
  8. 数量割引や価格の段階設定はありますか?
  9. サプライヤーが提示するリードタイムと納入実績はどのようなものですか?
  10. これは一度限りの購入ですか、それとも継続的な補充ですか?

関連スキル

  • supplier-selection:サプライヤーの評価と選定
  • strategic-sourcing:カテゴリー戦略と調達アプローチ
  • spend-analysis:支出パターンと機会の分析
  • inventory-optimization:安全在庫と再発注点
  • supplier-risk-management:サプライヤーリスクの監視
  • contract-management:最適な契約条件の交渉
  • demand-forecasting:調達計画における需要データの作成

コメント