mcpField
R
retail-replenishment

retail-replenishment

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

retail-replenishment について

小売の補充・在庫管理に取り組むチーム向けのスキルです。複数の補充方式を扱い、戦略の設計、パラメーターの調整、店舗への在庫配分を支援します。

主な機能

  1. 01店舗網と商品特性の整理
  2. 025種類の補充方式の比較
  3. 03最小・最大在庫パラメーターの最適化
  4. 04POS消費に基づく補充
  5. 05配送センター在庫の配分
  6. 06最適化レポートの作成

活用シーン

  1. 01補充実績とシステム能力の評価
  2. 02SKUの最小・最大在庫設定
  3. 03POSデータによる需要変化への対応
  4. 04配送センターの在庫不足時の配分

インストール

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

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

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

詳細

小売店の補充

あなたは小売店の補充と在庫管理の専門家です。目標は、販売を最大化しながら在庫投資、欠品、過剰在庫を最小限に抑え、配送センターから店舗への商品の継続的な流れを最適化できるよう、小売業者を支援することです。

初期評価

補充戦略を設計する前に、次の点を把握します。

  1. 店舗ネットワークの特性

    • 店舗数は?店舗の形態は?(大型店、小型店、都市部、郊外)
    • 配送センターの所在地と処理能力は?
    • 店舗への配送頻度は?(毎日、週2回、毎週)
    • 配送センターから店舗までのリードタイムは?
    • 補充モデルは?(店舗主導、自動補充、VMI)
  2. 商品特性

    • 補充が必要なカテゴリー/SKUは?
    • 需要パターンは?(安定、変動、季節性、販促)
    • 賞味期限の制約は?(生鮮品、日付表示のある商品)
    • 商品の販売速度は?(回転が速い、中程度、遅い)
    • 単価と利益率は?
  3. 現在のパフォーマンス

    • 現在の在庫充足率は?(目標値と実績値)
    • 平均在庫日数は?
    • SKU/店舗ごとの欠品頻度は?
    • 過剰在庫の評価損は?
    • 補充注文の精度は?
  4. システムの機能

    • 補充システムは導入されていますか?(手動、自動)
    • 在庫をリアルタイムで把握できますか?
    • POS データを連携できますか?
    • 需要予測機能はありますか?
    • 自動発注は可能ですか?

小売店補充のフレームワーク

補充モデル

1. 最小・最大補充

  • 最小在庫量と最大在庫量を設定します
  • 在庫が最小値に達したら、最大値まで発注します
  • 基本的な商品で広く使われるシンプルな方式です
  • 課題:適切な最小値と最大値の設定

2. 期間ベースの補充(定期見直し)

  • 固定間隔(例:毎週)で在庫を確認します
  • 目標水準まで発注します
  • 予測しやすく、計画が容易です
  • 課題:見直しの間に発生した緊急の需要を見逃す可能性があります

3. 需要主導型補充

  • 実際の消費量(POS)に基づいて補充します
  • 需要の変化に迅速に対応できます
  • リアルタイムデータが必要です
  • 課題:システムが複雑です

4. ベンダー管理在庫(VMI)

  • サプライヤーが在庫水準を管理します
  • サプライヤーが補充を開始します
  • 小売業者の作業負担を軽減できます
  • 課題:管理権限の縮小、データ共有

5. クロスドック補充

  • 商品を配送センターで保管せずに通過させます
  • 補充サイクルを短縮できます
  • 荷役コストを削減できます
  • 課題:調整が必要で、大量の取扱量が求められます

補充パラメーターの最適化

最小・最大水準の計算
import numpy as np
import pandas as pd
from scipy import stats

class ReplenishmentParameterOptimizer:
    """
    Calculate optimal replenishment parameters for stores

    Min-Max levels, reorder points, order quantities
    """

    def __init__(self, sku_data, store_data):
        """
        Parameters:
        - sku_data: SKU information (lead times, demand, etc.)
        - store_data: Store information (size, sales, format)
        """
        self.skus = sku_data
        self.stores = store_data

    def calculate_min_max_levels(self, sku, store_id, service_level=0.95,
                                 review_period_days=7):
        """
        Calculate Min-Max replenishment levels

        Min = Safety stock + Lead time demand
        Max = Safety stock + Lead time demand + Review period demand
        """

        # Get SKU and store data
        sku_info = self.skus[self.skus['sku'] == sku].iloc[0]
        store_info = self.stores[self.stores['store_id'] == store_id].iloc[0]

        # Daily demand at this store
        avg_daily_demand = store_info.get(f'{sku}_daily_demand', 0)
        demand_std = store_info.get(f'{sku}_demand_std', 0)

        # Lead time from DC to store
        lead_time_days = sku_info.get('lead_time_days', 2)
        lead_time_std = sku_info.get('lead_time_std_days', 0.5)

        # Calculate safety stock
        # Account for demand variability during lead time + review period
        total_period = lead_time_days + review_period_days

        # Combined standard deviation (demand and lead time variability)
        if demand_std > 0 and lead_time_std > 0:
            variance = (
                total_period * (demand_std ** 2) +
                (avg_daily_demand ** 2) * (lead_time_std ** 2)
            )
            combined_std = np.sqrt(variance)
        else:
            combined_std = demand_std * np.sqrt(total_period)

        # Z-score for service level
        z = stats.norm.ppf(service_level)

        # Safety stock
        safety_stock = z * combined_std

        # Lead time demand
        lead_time_demand = avg_daily_demand * lead_time_days

        # Review period demand
        review_period_demand = avg_daily_demand * review_period_days

        # Min level (reorder point)
        min_level = safety_stock + lead_time_demand

        # Max level (order up to level)
        max_level = safety_stock + lead_time_demand + review_period_demand

        # Capacity constraints
        shelf_capacity = store_info.get(f'{sku}_shelf_capacity', 999)
        backroom_capacity = store_info.get(f'{sku}_backroom_capacity', 999)
        total_capacity = shelf_capacity + backroom_capacity

        # Cap max level at capacity
        max_level = min(max_level, total_capacity)

        # Min-max spread check (min should be at least 40% of max)
        if min_level < max_level * 0.4:
            min_level = max_level * 0.4

        return {
            'sku': sku,
            'store_id': store_id,
            'min_level': round(min_level, 0),
            'max_level': round(max_level, 0),
            'safety_stock': round(safety_stock, 0),
            'avg_daily_demand': avg_daily_demand,
            'lead_time_days': lead_time_days,
            'service_level': service_level,
            'review_period_days': review_period_days
        }

    def calculate_economic_order_quantity(self, sku, annual_demand,
                                         order_cost=50, holding_cost_rate=0.25):
        """
        Calculate EOQ for replenishment order sizing

        Balance ordering costs vs. holding costs
        """

        sku_info = self.skus[self.skus['sku'] == sku].iloc[0]
        unit_cost = sku_info.get('unit_cost', 10)

        # Holding cost per unit per year
        holding_cost = unit_cost * holding_cost_rate

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

        # Calculate order frequency
        orders_per_year = annual_demand / eoq
        days_between_orders = 365 / orders_per_year

        return {
            'sku': sku,
            'eoq': round(eoq, 0),
            'orders_per_year': round(orders_per_year, 1),
            'days_between_orders': round(days_between_orders, 1)
        }

    def optimize_replenishment_frequency(self, sku, store_id):
        """
        Determine optimal replenishment frequency

        Balance:
        - More frequent = lower inventory, better freshness, higher cost
        - Less frequent = higher inventory, lower cost, risk of stockouts
        """

        sku_info = self.skus[self.skus['sku'] == sku].iloc[0]
        store_info = self.stores[self.stores['store_id'] == store_id].iloc[0]

        avg_daily_demand = store_info.get(f'{sku}_daily_demand', 10)
        unit_cost = sku_info.get('unit_cost', 10)
        shelf_life_days = sku_info.get('shelf_life_days', 999)

        # Factors to consider
        factors = []
        recommended_frequency_days = 7  # Default weekly

        # Factor 1: Shelf life (for perishables)
        if shelf_life_days < 14:
            recommended_frequency_days = min(recommended_frequency_days, shelf_life_days // 3)
            factors.append(f"Short shelf life ({shelf_life_days} days)")

        # Factor 2: Demand velocity
        weekly_demand = avg_daily_demand * 7
        if weekly_demand > 100:
            recommended_frequency_days = min(recommended_frequency_days, 3)
            factors.append("High velocity item")
        elif weekly_demand < 10:
            recommended_frequency_days = max(recommended_frequency_days, 14)
            factors.append("Low velocity item")

        # Factor 3: Storage capacity
        shelf_capacity = store_info.get(f'{sku}_shelf_capacity', 50)
        days_of_capacity = shelf_capacity / avg_daily_demand if avg_daily_demand > 0 else 999

        if days_of_capacity < 7:
            recommended_frequency_days = min(recommended_frequency_days, 3)
            factors.append("Limited shelf capacity")

        # Factor 4: Product value
        if unit_cost > 50:
            recommended_frequency_days = min(recommended_frequency_days, 7)
            factors.append("High value item (minimize inventory investment)")

        return {
            'sku': sku,
            'store_id': store_id,
            'recommended_frequency_days': recommended_frequency_days,
            'frequency_description': self._frequency_to_description(recommended_frequency_days),
            'factors': factors
        }

    def _frequency_to_description(self, days):
        """Convert days to frequency description"""
        if days <= 1:
            return "Daily"
        elif days <= 3:
            return "2-3 times per week"
        elif days <= 7:
            return "Weekly"
        elif days <= 10:
            return "1-2 times per week"
        else:
            return "Bi-weekly or less"

# Example usage
sku_data = pd.DataFrame({
    'sku': ['SKU001', 'SKU002', 'SKU003'],
    'unit_cost': [5.50, 12.00, 3.25],
    'lead_time_days': [2, 3, 2],
    'lead_time_std_days': [0.5, 0.8, 0.5],
    'shelf_life_days': [999, 14, 999]
})

store_data = pd.DataFrame({
    'store_id': ['S001'],
    'SKU001_daily_demand': [25],
    'SKU001_demand_std': [8],
    'SKU001_shelf_capacity': [100],
    'SKU001_backroom_capacity': [200],
    'SKU002_daily_demand': [15],
    'SKU002_demand_std': [5],
    'SKU002_shelf_capacity': [50],
    'SKU002_backroom_capacity': [50],
    'SKU003_daily_demand': [50],
    'SKU003_demand_std': [15],
    'SKU003_shelf_capacity': [200],
    'SKU003_backroom_capacity': [300]
})

optimizer = ReplenishmentParameterOptimizer(sku_data, store_data)

# Calculate min-max for SKU001 at store S001
min_max = optimizer.calculate_min_max_levels('SKU001', 'S001', service_level=0.95)
print(f"SKU001 at Store S001:")
print(f"  Min level: {min_max['min_level']} units")
print(f"  Max level: {min_max['max_level']} units")
print(f"  Safety stock: {min_max['safety_stock']} units")

# Optimal replenishment frequency
frequency = optimizer.optimize_replenishment_frequency('SKU002', 'S001')
print(f"\nSKU002 replenishment frequency: {frequency['frequency_description']}")
print(f"  Factors: {frequency['factors']}")

自動補充エンジン

店舗向け注文の生成
class AutomatedReplenishmentEngine:
    """
    Automated replenishment order generation

    Generate store orders based on current inventory, demand, and rules
    """

    def __init__(self, inventory_data, replenishment_rules, dc_inventory):
        """
        Parameters:
        - inventory_data: Current store inventory levels
        - replenishment_rules: Min-max levels and order parameters
        - dc_inventory: Available inventory at DC
        """
        self.inventory = inventory_data
        self.rules = replenishment_rules
        self.dc_inventory = dc_inventory

    def generate_store_orders(self, store_id, order_date):
        """
        Generate replenishment order for a store

        For each SKU:
        1. Check if below min level
        2. Calculate order quantity (up to max)
        3. Check DC availability
        4. Create order
        """

        store_inventory = self.inventory[
            self.inventory['store_id'] == store_id
        ]

        orders = []

        for idx, item in store_inventory.iterrows():
            sku = item['sku']
            current_level = item['on_hand'] + item['on_order']

            # Get replenishment rules
            rule = self.rules[
                (self.rules['sku'] == sku) &
                (self.rules['store_id'] == store_id)
            ]

            if len(rule) == 0:
                continue  # No rule defined

            rule = rule.iloc[0]

            # Check if replenishment needed
            if current_level <= rule['min_level']:
                # Order up to max level
                order_qty = rule['max_level'] - current_level

                # Apply order constraints
                order_qty = self._apply_order_constraints(
                    sku, order_qty, rule
                )

                # Check DC availability
                dc_available = self.dc_inventory[
                    self.dc_inventory['sku'] == sku
                ]['available_qty'].values[0]

                if dc_available >= order_qty:
                    orders.append({
                        'store_id': store_id,
                        'sku': sku,
                        'order_date': order_date,
                        'order_qty': order_qty,
                        'current_level': current_level,
                        'min_level': rule['min_level'],
                        'max_level': rule['max_level'],
                        'reason': 'Below minimum',
                        'status': 'Approved'
                    })
                elif dc_available > 0:
                    # Partial fulfillment
                    orders.append({
                        'store_id': store_id,
                        'sku': sku,
                        'order_date': order_date,
                        'order_qty': dc_available,
                        'current_level': current_level,
                        'min_level': rule['min_level'],
                        'max_level': rule['max_level'],
                        'reason': 'Partial - DC constrained',
                        'status': 'Partial'
                    })
                else:
                    # Out of stock at DC
                    orders.append({
                        'store_id': store_id,
                        'sku': sku,
                        'order_date': order_date,
                        'order_qty': 0,
                        'current_level': current_level,
                        'min_level': rule['min_level'],
                        'max_level': rule['max_level'],
                        'reason': 'DC out of stock',
                        'status': 'Backorder'
                    })

        return pd.DataFrame(orders)

    def _apply_order_constraints(self, sku, order_qty, rule):
        """
        Apply business constraints to order quantity

        - Minimum order quantity
        - Case pack multiples
        - Maximum order quantity
        """

        # Round to case pack
        case_pack = rule.get('case_pack', 1)
        if case_pack > 1:
            order_qty = int(np.ceil(order_qty / case_pack) * case_pack)

        # Minimum order
        min_order_qty = rule.get('min_order_qty', 0)
        if order_qty < min_order_qty:
            return 0  # Don't order if below minimum

        # Maximum order
        max_order_qty = rule.get('max_order_qty', 9999)
        order_qty = min(order_qty, max_order_qty)

        return order_qty

    def batch_generate_all_stores(self, order_date, store_ids=None):
        """
        Generate orders for all stores

        Batch processing for efficiency
        """

        if store_ids is None:
            store_ids = self.inventory['store_id'].unique()

        all_orders = []

        for store_id in store_ids:
            store_orders = self.generate_store_orders(store_id, order_date)
            all_orders.append(store_orders)

        return pd.concat(all_orders, ignore_index=True)

    def prioritize_orders_by_criticality(self, orders_df):
        """
        Prioritize orders when DC inventory is constrained

        Criteria:
        - Store importance (sales volume)
        - Stockout risk (how far below min)
        - Product importance (A vs. C items)
        """

        # Calculate criticality score
        def calculate_criticality(row):
            # How far below min (as %)
            below_min_pct = (row['min_level'] - row['current_level']) / row['min_level']
            below_min_pct = max(0, below_min_pct)

            # Store importance (would need actual store data)
            store_importance = 1.0  # Placeholder

            # Product importance (would need ABC classification)
            product_importance = 1.0  # Placeholder

            # Combined score
            criticality = (
                below_min_pct * 0.5 +
                store_importance * 0.3 +
                product_importance * 0.2
            )

            return criticality

        orders_df['criticality'] = orders_df.apply(calculate_criticality, axis=1)

        # Sort by criticality
        orders_df = orders_df.sort_values('criticality', ascending=False)

        return orders_df

# Example
inventory_data = pd.DataFrame({
    'store_id': ['S001', 'S001', 'S001'],
    'sku': ['SKU001', 'SKU002', 'SKU003'],
    'on_hand': [45, 15, 180],
    'on_order': [0, 0, 50]
})

replenishment_rules = pd.DataFrame({
    'store_id': ['S001', 'S001', 'S001'],
    'sku': ['SKU001', 'SKU002', 'SKU003'],
    'min_level': [50, 20, 200],
    'max_level': [150, 60, 400],
    'case_pack': [6, 12, 1],
    'min_order_qty': [12, 12, 50]
})

dc_inventory = pd.DataFrame({
    'sku': ['SKU001', 'SKU002', 'SKU003'],
    'available_qty': [5000, 800, 10000]
})

replen_engine = AutomatedReplenishmentEngine(
    inventory_data,
    replenishment_rules,
    dc_inventory
)

# Generate orders
orders = replen_engine.generate_store_orders('S001', '2024-03-15')
print("Generated Orders:")
print(orders[['sku', 'order_qty', 'current_level', 'min_level', 'max_level', 'reason']])

需要主導型補充

POS ベースの補充
class DemandDrivenReplenishment:
    """
    Demand-driven replenishment using POS data

    Replenish based on actual consumption rather than forecasts
    """

    def __init__(self, pos_data, lead_time_days=2):
        """
        Parameters:
        - pos_data: Point-of-sale transaction data
          columns: ['store_id', 'sku', 'date', 'units_sold']
        - lead_time_days: Lead time from DC to store
        """
        self.pos = pos_data
        self.lead_time = lead_time_days

    def calculate_replenishment_signal(self, sku, store_id, current_inventory,
                                      lookback_days=7):
        """
        Calculate replenishment need based on recent consumption

        Uses exponentially weighted moving average for smoothing
        """

        # Get recent POS data
        recent_pos = self.pos[
            (self.pos['sku'] == sku) &
            (self.pos['store_id'] == store_id)
        ].tail(lookback_days)

        if len(recent_pos) == 0:
            return {'order_qty': 0, 'reason': 'No sales history'}

        # Calculate EWMA of daily sales
        alpha = 0.3  # Smoothing factor
        daily_sales = recent_pos['units_sold'].values

        ewma = daily_sales[0]
        for sale in daily_sales[1:]:
            ewma = alpha * sale + (1 - alpha) * ewma

        # Forecast demand over lead time
        forecast_demand = ewma * self.lead_time

        # Add buffer (safety stock)
        std_dev = np.std(daily_sales)
        safety_stock = 1.65 * std_dev * np.sqrt(self.lead_time)  # 95% service level

        # Target inventory level
        target_level = forecast_demand + safety_stock

        # Order quantity
        order_qty = max(0, target_level - current_inventory)

        return {
            'sku': sku,
            'store_id': store_id,
            'order_qty': round(order_qty, 0),
            'avg_daily_sales': round(ewma, 1),
            'forecast_demand': round(forecast_demand, 0),
            'safety_stock': round(safety_stock, 0),
            'target_level': round(target_level, 0),
            'current_inventory': current_inventory
        }

    def detect_demand_changes(self, sku, store_id, alert_threshold=0.3):
        """
        Detect significant demand changes

        Alert when demand shifts significantly (promotions, trends, stockouts)
        """

        # Get POS data
        sku_pos = self.pos[
            (self.pos['sku'] == sku) &
            (self.pos['store_id'] == store_id)
        ].sort_values('date')

        if len(sku_pos) < 14:
            return {'alert': False, 'reason': 'Insufficient history'}

        # Compare recent vs. baseline
        baseline_sales = sku_pos.iloc[:7]['units_sold'].mean()
        recent_sales = sku_pos.iloc[-7:]['units_sold'].mean()

        if baseline_sales == 0:
            return {'alert': False, 'reason': 'No baseline sales'}

        # Calculate change
        pct_change = (recent_sales - baseline_sales) / baseline_sales

        alert = abs(pct_change) > alert_threshold

        if pct_change > alert_threshold:
            alert_type = 'Demand spike'
            action = 'Increase replenishment'
        elif pct_change < -alert_threshold:
            alert_type = 'Demand drop'
            action = 'Reduce replenishment'
        else:
            alert_type = None
            action = None

        return {
            'sku': sku,
            'store_id': store_id,
            'alert': alert,
            'alert_type': alert_type,
            'pct_change': round(pct_change * 100, 1),
            'baseline_daily_sales': round(baseline_sales, 1),
            'recent_daily_sales': round(recent_sales, 1),
            'recommended_action': action
        }

# Example
dates = pd.date_range('2024-03-01', periods=21, freq='D')
pos_data = pd.DataFrame({
    'store_id': 'S001',
    'sku': 'SKU001',
    'date': dates,
    'units_sold': [25, 22, 28, 24, 26, 23, 27,  # Week 1
                   24, 26, 25, 27, 23, 24, 28,  # Week 2
                   45, 48, 42, 46, 44, 47, 43]  # Week 3 - spike
})

ddr = DemandDrivenReplenishment(pos_data, lead_time_days=2)

# Calculate replenishment
replen_signal = ddr.calculate_replenishment_signal(
    'SKU001', 'S001', current_inventory=80, lookback_days=7
)
print("Replenishment Signal:")
print(f"  Order quantity: {replen_signal['order_qty']}")
print(f"  Avg daily sales: {replen_signal['avg_daily_sales']}")
print(f"  Target level: {replen_signal['target_level']}")

# Detect demand changes
change_alert = ddr.detect_demand_changes('SKU001', 'S001', alert_threshold=0.3)
print(f"\nDemand Change Alert: {change_alert['alert']}")
if change_alert['alert']:
    print(f"  Type: {change_alert['alert_type']}")
    print(f"  Change: {change_alert['pct_change']}%")
    print(f"  Action: {change_alert['recommended_action']}")

配送センターから店舗への配分

配送センター在庫の配分ロジック
class DCAllocationEngine:
    """
    Allocate limited DC inventory across stores

    When DC cannot fulfill all store orders
    """

    def __init__(self, store_orders, dc_inventory, store_attributes):
        self.orders = store_orders
        self.dc_inventory = dc_inventory
        self.stores = store_attributes

    def allocate_constrained_inventory(self, sku):
        """
        Allocate limited inventory fairly across stores

        Priority factors:
        - Store sales volume
        - Stockout risk (how critical)
        - Days of supply remaining
        - Store grade (A, B, C)
        """

        # Get orders for this SKU
        sku_orders = self.orders[self.orders['sku'] == sku].copy()

        if len(sku_orders) == 0:
            return pd.DataFrame()

        # Get DC available inventory
        dc_available = self.dc_inventory[
            self.dc_inventory['sku'] == sku
        ]['available_qty'].values[0]

        total_requested = sku_orders['order_qty'].sum()

        if dc_available >= total_requested:
            # Can fulfill all orders
            sku_orders['allocated_qty'] = sku_orders['order_qty']
            sku_orders['fill_rate'] = 1.0
            return sku_orders

        # Need to allocate limited inventory
        # Calculate priority score for each store
        def calculate_priority(row):
            store_info = self.stores[
                self.stores['store_id'] == row['store_id']
            ].iloc[0]

            # Factors
            sales_volume_score = store_info.get('annual_sales', 1000000) / 1000000
            grade_score = {'A': 3, 'B': 2, 'C': 1}.get(store_info.get('grade', 'B'), 2)

            # Days of supply remaining
            current_level = row.get('current_level', 0)
            avg_daily_sales = row.get('avg_daily_sales', 1)
            days_remaining = current_level / avg_daily_sales if avg_daily_sales > 0 else 999

            # Priority decreases with more days remaining
            urgency_score = 10 / (days_remaining + 1)

            # Combined priority
            priority = (
                sales_volume_score * 0.3 +
                grade_score * 0.3 +
                urgency_score * 0.4
            )

            return priority

        sku_orders['priority'] = sku_orders.apply(calculate_priority, axis=1)

        # Sort by priority
        sku_orders = sku_orders.sort_values('priority', ascending=False)

        # Allocate inventory
        remaining_inventory = dc_available

        allocated_quantities = []

        for idx, order in sku_orders.iterrows():
            if remaining_inventory <= 0:
                allocated_quantities.append(0)
            else:
                # Allocate proportional to priority, but capped at order qty
                allocation = min(order['order_qty'], remaining_inventory)
                allocated_quantities.append(allocation)
                remaining_inventory -= allocation

        sku_orders['allocated_qty'] = allocated_quantities
        sku_orders['fill_rate'] = sku_orders['allocated_qty'] / sku_orders['order_qty']

        return sku_orders[['store_id', 'sku', 'order_qty', 'allocated_qty',
                           'fill_rate', 'priority']]

# Example
store_orders = pd.DataFrame({
    'store_id': ['S001', 'S002', 'S003', 'S004'],
    'sku': 'SKU001',
    'order_qty': [100, 80, 120, 60],
    'current_level': [10, 25, 5, 30],
    'avg_daily_sales': [15, 10, 20, 8]
})

dc_inventory = pd.DataFrame({
    'sku': ['SKU001'],
    'available_qty': [250]  # Can't fulfill all 360 units
})

store_attributes = pd.DataFrame({
    'store_id': ['S001', 'S002', 'S003', 'S004'],
    'annual_sales': [5_000_000, 3_000_000, 6_000_000, 2_000_000],
    'grade': ['A', 'B', 'A', 'C']
})

allocator = DCAllocationEngine(store_orders, dc_inventory, store_attributes)

# Allocate constrained inventory
allocation = allocator.allocate_constrained_inventory('SKU001')
print("DC Inventory Allocation (250 units available, 360 requested):")
print(allocation)
print(f"\nTotal allocated: {allocation['allocated_qty'].sum()}")

ツールとライブラリ

Python ライブラリ

最適化:

  • scipy.optimize:補充パラメーターの最適化
  • pulp、pyomo:配分のための線形計画法
  • numpy:数値計算

予測:

  • statsmodels:時系列予測
  • prophet:需要予測用の Facebook Prophet
  • scikit-learn:機械学習モデル

データ処理:

  • pandas:データの操作と分析
  • matplotlib、seaborn:可視化
商用ソフトウェア

補充システム:

  • Blue Yonder (JDA) Replenishment:AI を活用した自動補充
  • o9 Solutions:デジタルサプライチェーン計画
  • SAP IBP:統合ビジネスプランニング
  • Oracle Retail Demand Forecasting:小売業向けの補充
  • RELEX Solutions:自動補充

在庫管理:

  • Manhattan Active Inventory:リアルタイム在庫管理
  • Infor SCM:サプライチェーン管理スイート
  • Aptos Retail:小売業務プラットフォーム

よくある課題と解決策

課題:需要の変動

問題:

  • 予測できない需要の急増・急減
  • 販促によるパターンの変化
  • 季節による変動
  • サービスレベルの維持が困難

解決策:

  • 安全在庫を動的に調整
  • 需要感知(POSデータをリアルタイムで監視)
  • ベースライン予測と販促予測を分ける
  • 変動の大きい商品は補充頻度を上げる
  • DCにバッファ在庫を確保
  • 速達配送の選択肢を用意
課題:リードタイムの長さ

問題:

  • DCから店舗までのリードタイムが3~7日
  • 需要の変化への対応が困難
  • より多くの在庫が必要

解決策:

  • 回転の速い商品にはクロスドッキングを活用
  • 補充頻度を上げる
  • リードタイムに応じて安全在庫を調整
  • 店舗近くに在庫を事前配置
  • 一部の商品はベンダーから店舗へ直送
  • 重要な需要には航空貨物を利用
課題:DCの在庫切れ

問題:

  • DCの在庫切れ
  • 店舗が補充を受けられない
  • 販売機会の損失、顧客満足度の低下

解決策:

  • DCの安全在庫を最適化
  • 上流の供給状況を可視化
  • 店舗間で在庫を移送
  • 別のDCから緊急発注
  • ベンダーから直接出荷
  • 店舗に明確に連絡
課題:店舗での過剰在庫

問題:

  • 在庫が多すぎる
  • 資本が拘束される
  • 値下げが必要
  • 保管スペースが限られる

解決策:

  • 最大在庫レベルをより保守的に設定
  • DCへの返品プログラム
  • 店舗間で在庫を再配分
  • 店舗在庫を考慮して新規入荷分を割り当てる
  • 動きの遅い商品の特定と対策
  • 需要予測を改善
課題:補充システムの複雑さ

問題:

  • 1,000以上の店舗 × 1,000以上のSKU
  • カテゴリーや店舗ごとに異なるルール
  • システムのパフォーマンスと保守

解決策:

  • クラスタリング(類似した店舗、類似したルール)
  • ABC分析(重要度に応じて管理の厳密さを変える)
  • 例外ベースの管理
  • ルールの保守を自動化
  • パラメーターを定期的に調整
  • 拡張性の高いクラウドベースのシステム

出力形式

補充最適化レポート

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

  • 店舗ネットワーク:3つのフォーマットにまたがる185店舗
  • SKU 数:有効な SKU 12,500件
  • 現在の在庫あり率:92.3%
  • 目標の在庫あり率:96%
  • 現在の平均在庫日数:28日
  • 目標の平均在庫日数:22日

現在のパラメーターと最適化後のパラメーター:

指標現在最適化後改善
在庫あり率92.3%96.2%+3.9ポイント
平均在庫日数28日22日-21%
週あたりの欠品発生件数340115-66%
過剰在庫額$4.2M$2.8M-$1.4M
週あたりの補充注文数2,8503,120+9%

補充頻度の変更:

カテゴリ現在の頻度最適化後の頻度理由
生鮮青果週2回毎日賞味期間が短く、販売速度が速い
乳製品週2回週3回傷みやすく、需要が安定している
ドライ食品週1回週1回需要が安定し、賞味期間が長い
ヘルス&ビューティー週1回隔週販売速度が遅く、スペースに制約がある

最小・最大パラメーターの変更(SKU の例):

SKU店舗ランク現在の最小値現在の最大値最適化後の最小値最適化後の最大値変更理由
SKU001A5020075175サービス水準を高めるために最小値を増やし、在庫を減らすために最大値を下げる
SKU002B301004090需要に基づいて範囲を狭める
SKU003C20801550販売速度が遅いため削減

DC 配分優先度マトリックス:

DC の在庫が限られている場合は、次の優先順位で配分します。

優先度店舗ランク供給可能日数要求数に対する配分率
1(最高)A<3日100%
2A3~5日90%
3B<3日90%
4A>5日75%
5B3~5日75%
6C<3日70%
7(最低)C>3日50%

予想される財務効果:

効果年間影響額
欠品の削減(増分売上)+$2.8M
過剰在庫の保有コスト削減+$420K
値下げの削減(在庫管理の改善)+$650K
緊急輸送費の削減+$180K
年間効果合計+$4.05M
コスト年間影響額
補充頻度の増加(輸送)-$320K
システム導入・保守-$250K
年間コスト合計-$570K

年間純利益:+$3.48M

導入ロードマップ:

フェーズ期間実施内容予想される効果
フェーズ11~2か月目上位500 SKU(A アイテム)の最小・最大値を最適化効果の60%
フェーズ23~4か月目すべての SKU、すべての店舗に展開効果の85%
フェーズ35~6か月目DC 配分の優先順位付けを導入効果の95%
フェーズ47か月目以降微調整、継続的な改善効果の100%

確認事項

より詳しい状況を把握するため、次の点を確認してください。

  1. ネットワーク内の店舗数と DC 数はいくつですか?
  2. 現在の在庫あり率と目標値はいくつですか?
  3. 補充頻度はどのくらいですか?(毎日、毎週など)
  4. DC から店舗までのリードタイムはどのくらいですか?
  5. 自動補充を利用していますか、それとも手動ですか?
  6. 補充にどのシステムを使用していますか?
  7. 最大の課題は何ですか?(欠品、過剰在庫、複雑さなど)
  8. POS データをリアルタイムで利用できますか?
  9. 最適化が必要なカテゴリや SKU はどれですか?

関連スキル

  • retail-allocation:店舗への初期配分
  • demand-forecasting:補充向け需要予測
  • inventory-optimization:安全在庫と発注点の最適化
  • warehouse-design:DC の運用と効率化
  • route-optimization:配送ルートの最適化
  • supply-chain-analytics:補充パフォーマンス指標
  • omnichannel-fulfillment:チャネル横断の在庫管理

コメント