mcpField
W
warehouse-slotting-optimization

warehouse-slotting-optimization

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

关于 warehouse-slotting-optimization

用于为 SKU 分配仓库储位的优化技能。它结合拣选频率、商品关联、空间和运营限制,辅助规划布局以减少行走距离并提升拣选效率。

主要功能

  1. 01ABC 周转分析
  2. 02关联拣选分析
  3. 03SKU 储位分配
  4. 04按需求动态调整储位
  5. 05规划前置拣选位容量

适用场景

  1. 01将高周转 SKU 放在易取位置
  2. 02将经常同时拣选的商品放在一起
  3. 03需求变化后重新安排储位
  4. 04根据补货频率确定前置拣选位容量

安装

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

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

安装后在 Claude Code 中输入/warehouse-slotting-optimization确认已加载。

详细介绍

仓库库位优化

你是仓库库位优化和存储位置分配方面的专家。你的目标是帮助优化 SKU 在仓库库位中的摆放,以缩短行走距离、提高拣选效率、最大化空间利用率并降低人工成本。

初步评估

优化库位前,先了解以下信息:

  1. 仓库特征

    • 仓库布局如何?(区域、通道、层级)
    • 存储位置和容量总数是多少?
    • 拣选区域有哪些?(前置拣选区、储备区、散货区)
    • 使用哪些设备?(叉车、拣选设备、自动化系统)
    • 当前采用什么库位规划方法?(人工、ABC 分析、随机)
  2. 产品概况

    • SKU 数量是多少?
    • 产品尺寸和重量是多少?
    • 出库速度如何?(每日/每周拣选次数)
    • 季节性和需求模式如何?
    • 货物体积流量如何?(立方英尺 × 拣选次数)
  3. 运营限制

    • 是否有 FIFO/FEFO 要求?
    • 是否有危险品或兼容性限制?
    • 是否有温控区域?
    • 每个库位/层级的最大承重是多少?
    • 补货频率如何?
  4. 业务目标

    • 首要目标是什么?(缩短行走距离、平衡工作量、最大化吞吐量)
    • 当前的拣选效率指标如何?
    • 预期的订单结构会有哪些变化?
    • 库位规划的刷新频率如何?

库位优化框架

库位规划原则

1. 黄金区域原则

  • 将高周转商品放在最易取用的位置
  • 视线高度(腰部至肩部高度)最理想
  • 尽量减少弯腰和伸手
  • 减轻拣选人员的疲劳

2. 单位订单体积指标(COI)

  • 公式:COI = 立方体积 / 每个周期的订单数
  • COI 越低 = 每立方英尺的拣选次数越多 = 越适合作为库位候选项
  • 优先将高拣选量、低体积的商品放在前置拣选区

3. ABC 周转速度分析

  • A 类商品:前 20% 的 SKU,占 80% 的拣选量 → 黄金库位
  • B 类商品:接下来的 30% SKU,占 15% 的拣选量 → 次优库位
  • C 类商品:后 50% 的 SKU,占 5% 的拣选量 → 储备库位

4. 相关拣选

  • 经常一起订购的商品应相邻摆放
  • 减少各拣选点之间的行走距离
  • 基于订单历史进行聚类分析

5. 产品关联性

  • 将相似产品分组(尺寸、类别、搬运方式)
  • 提高补货效率
  • 便于交叉培训

数学模型

二次分配问题(QAP)

库位优化从根本上说是一个二次分配问题:

决策变量:

  • x[i,j] = 若 SKU i 分配到库位 j,则为 1,否则为 0

目标: 最小化按拣选频率加权的总行走距离

minimize: Σ Σ Σ Σ (f[i,k] × d[j,l] × x[i,j] × x[k,l])
          i k j l

where:
- f[i,k] = frequency SKU i and k are picked together
- d[j,l] = distance between location j and l
- x[i,j] = 1 if SKU i assigned to location j

约束条件:

# 1. Each SKU assigned to exactly one location
for i in SKUs:
    Σ x[i,j] = 1  for all j in Locations

# 2. Each location holds at most one SKU (can be relaxed)
for j in Locations:
    Σ x[i,j] ≤ 1  for all i in SKUs

# 3. Capacity constraints
for j in Locations:
    Σ (size[i] × x[i,j]) ≤ capacity[j]  for all i

# 4. Compatibility constraints (hazmat, temperature, etc.)
for i in SKUs:
    for j in Locations:
        if not compatible(i, j):
            x[i,j] = 0

库位规划算法

ABC 库位规划(基础)
import pandas as pd
import numpy as np

def abc_slotting_analysis(sku_data):
    """
    Perform ABC analysis for warehouse slotting

    Parameters:
    -----------
    sku_data : DataFrame with columns
        - sku_id: Product identifier
        - picks_per_month: Number of picks
        - cube_per_unit: Cubic feet per unit
        - units_per_pick: Average units per pick

    Returns:
    --------
    DataFrame with ABC classification and slotting recommendations
    """

    df = sku_data.copy()

    # Calculate total cube moved
    df['cube_moved'] = (df['picks_per_month'] *
                        df['units_per_pick'] *
                        df['cube_per_unit'])

    # Calculate Cube-per-Order Index (COI)
    df['coi'] = df['cube_per_unit'] / (df['picks_per_month'] + 1)

    # Sort by picks (velocity)
    df = df.sort_values('picks_per_month', ascending=False)

    # Calculate cumulative percentage of picks
    df['cumulative_picks'] = df['picks_per_month'].cumsum()
    total_picks = df['picks_per_month'].sum()
    df['pick_percentage'] = (df['cumulative_picks'] / total_picks) * 100

    # ABC Classification
    df['abc_class'] = pd.cut(
        df['pick_percentage'],
        bins=[0, 80, 95, 100],
        labels=['A', 'B', 'C']
    )

    # Slotting recommendations
    def get_zone_recommendation(row):
        if row['abc_class'] == 'A':
            if row['coi'] < 0.1:
                return 'Forward Pick - Golden Zone'
            else:
                return 'Forward Pick - Lower Priority'
        elif row['abc_class'] == 'B':
            return 'Forward Pick - Edge Locations'
        else:
            return 'Reserve Storage'

    df['recommended_zone'] = df.apply(get_zone_recommendation, axis=1)

    # Priority score (lower is better)
    df['priority_score'] = (
        df['pick_percentage'].rank() * 0.5 +
        df['coi'].rank(ascending=False) * 0.3 +
        df['cube_moved'].rank(ascending=False) * 0.2
    )

    return df.sort_values('priority_score')


# Example usage
sku_data = pd.DataFrame({
    'sku_id': [f'SKU{i:04d}' for i in range(1, 101)],
    'picks_per_month': np.random.randint(1, 1000, 100),
    'cube_per_unit': np.random.uniform(0.5, 10, 100),
    'units_per_pick': np.random.randint(1, 5, 100)
})

slotting_analysis = abc_slotting_analysis(sku_data)

print("Top 10 SKUs for Golden Zone:")
print(slotting_analysis.head(10)[['sku_id', 'picks_per_month',
                                    'coi', 'abc_class', 'recommended_zone']])

print(f"\nABC Distribution:")
print(slotting_analysis['abc_class'].value_counts())
相关拣选分析
from sklearn.cluster import KMeans
from scipy.spatial.distance import pdist, squareform

def analyze_pick_correlation(order_lines_data):
    """
    Analyze which SKUs are frequently picked together

    Parameters:
    -----------
    order_lines_data : DataFrame with columns
        - order_id: Order identifier
        - sku_id: Product identifier
        - quantity: Units picked

    Returns:
    --------
    Correlation matrix and clustered SKU groups
    """

    # Create SKU co-occurrence matrix
    pivot = order_lines_data.pivot_table(
        index='order_id',
        columns='sku_id',
        values='quantity',
        fill_value=0,
        aggfunc='sum'
    )

    # Binary: SKU was in order or not
    binary_matrix = (pivot > 0).astype(int)

    # Calculate correlation between SKUs
    correlation_matrix = binary_matrix.corr()

    # Calculate pick affinity score
    # How often SKU i and j appear together / total orders
    n_orders = len(pivot)
    co_occurrence = binary_matrix.T.dot(binary_matrix)
    affinity_matrix = co_occurrence / n_orders

    # Cluster SKUs based on pick patterns
    n_clusters = min(10, len(pivot.columns) // 5)

    if len(pivot.columns) > 1:
        kmeans = KMeans(n_clusters=n_clusters, random_state=42)
        clusters = kmeans.fit_predict(binary_matrix.T)

        sku_clusters = pd.DataFrame({
            'sku_id': pivot.columns,
            'cluster': clusters
        })

        # Add cluster size
        cluster_sizes = sku_clusters['cluster'].value_counts()
        sku_clusters['cluster_size'] = sku_clusters['cluster'].map(cluster_sizes)

    else:
        sku_clusters = pd.DataFrame({
            'sku_id': pivot.columns,
            'cluster': 0,
            'cluster_size': len(pivot.columns)
        })

    return {
        'correlation_matrix': correlation_matrix,
        'affinity_matrix': affinity_matrix,
        'sku_clusters': sku_clusters,
        'binary_matrix': binary_matrix
    }


def get_top_correlations(correlation_matrix, top_n=20):
    """Get top N SKU pairs with highest correlation"""

    # Get upper triangle (avoid duplicates)
    mask = np.triu(np.ones_like(correlation_matrix), k=1).astype(bool)
    upper_tri = correlation_matrix.where(mask)

    # Stack and sort
    correlations = upper_tri.stack().sort_values(ascending=False)

    return correlations.head(top_n)


# Example usage
order_lines = pd.DataFrame({
    'order_id': [1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 5],
    'sku_id': ['A', 'B', 'C', 'A', 'B', 'A', 'D', 'E', 'B', 'C', 'A', 'B', 'C', 'F'],
    'quantity': [1, 2, 1, 1, 1, 2, 1, 1, 1, 3, 1, 1, 1, 2]
})

correlation_analysis = analyze_pick_correlation(order_lines)
print("SKU Clusters (items to slot near each other):")
print(correlation_analysis['sku_clusters'].sort_values('cluster'))

top_corr = get_top_correlations(correlation_analysis['correlation_matrix'])
print("\nTop Correlated SKU Pairs:")
print(top_corr)
优化模型:库位分配
from pulp import *
import numpy as np

def optimize_slotting(skus, locations, pick_frequencies, distances,
                      sku_sizes, location_capacities, constraints=None):
    """
    Optimize warehouse slotting using Mixed-Integer Programming

    Parameters:
    -----------
    skus : list
        SKU identifiers
    locations : list
        Location identifiers
    pick_frequencies : dict
        {sku: picks_per_period}
    distances : dict
        {location: distance_from_depot} or distance matrix
    sku_sizes : dict
        {sku: cubic_feet}
    location_capacities : dict
        {location: max_cubic_feet}
    constraints : dict, optional
        Additional constraints (compatibility, etc.)

    Returns:
    --------
    Optimal slotting assignment
    """

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

    # Decision variables: x[i,j] = 1 if SKU i assigned to location j
    x = LpVariable.dicts("assign",
                         [(i, j) for i in skus for j in locations],
                         cat='Binary')

    # Objective: Minimize total travel distance weighted by pick frequency
    # Assuming distance is from central depot/staging area to location
    prob += lpSum([
        pick_frequencies.get(i, 0) * distances.get(j, 0) * x[i, j]
        for i in skus for j in locations
    ]), "Total_Weighted_Distance"

    # Constraints

    # 1. Each SKU must be assigned to exactly one location
    for i in skus:
        prob += lpSum([x[i, j] for j in locations]) == 1, f"SKU_{i}_Assignment"

    # 2. Each location can hold at most one SKU (can be relaxed)
    for j in locations:
        prob += lpSum([x[i, j] for i in skus]) <= 1, f"Location_{j}_Capacity_Count"

    # 3. Physical capacity constraints
    for j in locations:
        prob += lpSum([
            sku_sizes.get(i, 0) * x[i, j] for i in skus
        ]) <= location_capacities.get(j, float('inf')), f"Location_{j}_Volume"

    # 4. Additional constraints (if provided)
    if constraints and 'incompatible' in constraints:
        for (sku, loc) in constraints['incompatible']:
            if sku in skus and loc in locations:
                prob += x[sku, loc] == 0, f"Incompatible_{sku}_{loc}"

    if constraints and 'required' in constraints:
        for (sku, loc) in constraints['required']:
            if sku in skus and loc in locations:
                prob += x[sku, loc] == 1, f"Required_{sku}_{loc}"

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

    # Extract solution
    assignments = {}
    for i in skus:
        for j in locations:
            if x[i, j].varValue > 0.5:
                assignments[i] = j
                break

    # Calculate metrics
    total_distance = sum(
        pick_frequencies.get(sku, 0) * distances.get(loc, 0)
        for sku, loc in assignments.items()
    )

    utilization = {}
    for j in locations:
        used = sum(
            sku_sizes.get(i, 0)
            for i, assigned_loc in assignments.items()
            if assigned_loc == j
        )
        capacity = location_capacities.get(j, 1)
        utilization[j] = (used / capacity * 100) if capacity > 0 else 0

    return {
        'status': LpStatus[prob.status],
        'assignments': assignments,
        'total_weighted_distance': total_distance,
        'location_utilization': utilization,
        'objective_value': value(prob.objective)
    }


# Example usage
skus = [f'SKU{i}' for i in range(1, 21)]
locations = [f'A{i:02d}' for i in range(1, 26)]  # 25 locations for 20 SKUs

# Generate sample data
np.random.seed(42)
pick_frequencies = {sku: np.random.randint(10, 1000) for sku in skus}
distances = {loc: np.random.uniform(10, 100) for loc in locations}
sku_sizes = {sku: np.random.uniform(1, 5) for sku in skus}
location_capacities = {loc: 10.0 for loc in locations}

# Add some constraints
constraints = {
    'incompatible': [('SKU1', 'A01'), ('SKU5', 'A25')],  # Example incompatibilities
}

result = optimize_slotting(
    skus, locations, pick_frequencies, distances,
    sku_sizes, location_capacities, constraints
)

print(f"Optimization Status: {result['status']}")
print(f"Total Weighted Distance: {result['total_weighted_distance']:,.2f}")
print(f"\nTop 10 Assignments (by pick frequency):")

# Sort by pick frequency
sorted_skus = sorted(skus, key=lambda s: pick_frequencies[s], reverse=True)[:10]
for sku in sorted_skus:
    loc = result['assignments'][sku]
    print(f"  {sku} → {loc} (picks: {pick_frequencies[sku]}, distance: {distances[loc]:.1f})")
用于简单分配的匈牙利算法
from scipy.optimize import linear_sum_assignment
import numpy as np

def hungarian_slotting(skus, locations, cost_matrix):
    """
    Use Hungarian algorithm for simple one-to-one slotting

    Parameters:
    -----------
    skus : list
        SKU identifiers
    locations : list
        Location identifiers
    cost_matrix : 2D array
        Cost[i,j] = cost of assigning sku i to location j

    Returns:
    --------
    Optimal assignment (when # SKUs = # locations)
    """

    # Ensure square matrix (pad if needed)
    n_skus = len(skus)
    n_locs = len(locations)

    if n_skus != n_locs:
        # Pad with dummy SKUs or locations
        max_dim = max(n_skus, n_locs)
        padded_cost = np.full((max_dim, max_dim), np.max(cost_matrix) * 10)
        padded_cost[:n_skus, :n_locs] = cost_matrix
        cost_matrix = padded_cost

    # Solve using Hungarian algorithm
    row_ind, col_ind = linear_sum_assignment(cost_matrix)

    # Extract valid assignments (not dummy)
    assignments = {}
    total_cost = 0

    for i, j in zip(row_ind, col_ind):
        if i < n_skus and j < n_locs:
            assignments[skus[i]] = locations[j]
            total_cost += cost_matrix[i, j]

    return {
        'assignments': assignments,
        'total_cost': total_cost,
        'row_indices': row_ind[:n_skus],
        'col_indices': col_ind[:n_skus]
    }


# Example: Assign 10 SKUs to 10 best locations
skus = [f'SKU{i}' for i in range(1, 11)]
locations = [f'LOC{i}' for i in range(1, 11)]

# Cost = Pick frequency × Distance
# Lower cost = better assignment
np.random.seed(42)
pick_freq = np.random.randint(50, 500, 10)
distances = np.random.uniform(5, 50, 10)

# Cost matrix: each row is a SKU, each column is a location
cost_matrix = np.outer(pick_freq, distances)

result = hungarian_slotting(skus, locations, cost_matrix)

print("Hungarian Algorithm Assignment:")
print(f"Total Cost: {result['total_cost']:,.2f}")
print("\nAssignments:")
for sku, loc in result['assignments'].items():
    i = skus.index(sku)
    j = locations.index(loc)
    print(f"  {sku} → {loc} (cost: {cost_matrix[i,j]:,.2f})")

高级库位规划技术

动态库位规划
import pandas as pd
from datetime import datetime, timedelta

class DynamicSlottingOptimizer:
    """
    Dynamic slotting that adapts to changing demand patterns
    """

    def __init__(self, warehouse_layout, refresh_frequency='weekly'):
        self.warehouse_layout = warehouse_layout
        self.refresh_frequency = refresh_frequency
        self.slotting_history = []
        self.current_slotting = {}

    def calculate_velocity_trend(self, historical_picks, window_days=30):
        """
        Calculate velocity trends for each SKU

        Returns trending up, stable, or trending down
        """

        df = historical_picks.copy()
        df['date'] = pd.to_datetime(df['date'])

        # Calculate rolling average
        recent_end = df['date'].max()
        recent_start = recent_end - timedelta(days=window_days)
        older_start = recent_start - timedelta(days=window_days)

        recent_picks = df[df['date'] >= recent_start].groupby('sku_id')['picks'].sum()
        older_picks = df[
            (df['date'] >= older_start) & (df['date'] < recent_start)
        ].groupby('sku_id')['picks'].sum()

        velocity_change = (recent_picks - older_picks) / (older_picks + 1)

        trends = {}
        for sku in velocity_change.index:
            change = velocity_change[sku]
            if change > 0.2:
                trends[sku] = 'trending_up'
            elif change < -0.2:
                trends[sku] = 'trending_down'
            else:
                trends[sku] = 'stable'

        return trends

    def seasonal_adjustment(self, sku_id, current_month):
        """
        Apply seasonal factors to velocity
        """

        # Example seasonal factors (would be learned from historical data)
        seasonal_factors = {
            'toy_sku': {11: 2.5, 12: 3.0, 1: 0.5},  # Holiday season
            'apparel_sku': {3: 1.5, 4: 1.3, 9: 1.4, 10: 1.2},  # Spring/Fall
        }

        # Default factor
        return seasonal_factors.get(sku_id, {}).get(current_month, 1.0)

    def recommend_reslotting(self, current_slotting, velocity_trends,
                            threshold_change=0.3):
        """
        Recommend which SKUs should be reslotted
        """

        reslot_candidates = []

        for sku, current_zone in current_slotting.items():
            trend = velocity_trends.get(sku, 'stable')

            # High priority: trending up items in poor locations
            if trend == 'trending_up' and current_zone in ['Reserve', 'C_Zone']:
                reslot_candidates.append({
                    'sku': sku,
                    'reason': 'Trending up, needs better location',
                    'priority': 'High',
                    'current_zone': current_zone,
                    'recommended_zone': 'Forward Pick'
                })

            # Low priority: trending down items in prime locations
            elif trend == 'trending_down' and current_zone == 'Golden Zone':
                reslot_candidates.append({
                    'sku': sku,
                    'reason': 'Trending down, free up prime space',
                    'priority': 'Medium',
                    'current_zone': current_zone,
                    'recommended_zone': 'B_Zone or Reserve'
                })

        return pd.DataFrame(reslot_candidates)

    def minimize_disruption(self, old_slotting, new_slotting, max_moves=50):
        """
        Minimize number of moves when implementing new slotting

        Use cycle counting approach: only move most impactful SKUs
        """

        moves_needed = []

        for sku in new_slotting:
            old_loc = old_slotting.get(sku)
            new_loc = new_slotting.get(sku)

            if old_loc != new_loc:
                # Calculate impact of this move
                impact = self.calculate_move_impact(sku, old_loc, new_loc)
                moves_needed.append({
                    'sku': sku,
                    'from': old_loc,
                    'to': new_loc,
                    'impact': impact
                })

        # Sort by impact, take top N moves
        moves_df = pd.DataFrame(moves_needed)
        if len(moves_df) > 0:
            moves_df = moves_df.sort_values('impact', ascending=False)
            return moves_df.head(max_moves)

        return moves_df

    def calculate_move_impact(self, sku, old_loc, new_loc):
        """
        Estimate benefit of moving SKU from old to new location
        """
        # Simplified: would use actual distances and pick frequencies
        # Higher impact = more important to move
        return np.random.uniform(0, 100)  # Placeholder


# Example usage
optimizer = DynamicSlottingOptimizer(warehouse_layout={})

# Simulate historical picks
historical_data = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=90),
    'sku_id': np.random.choice(['SKU1', 'SKU2', 'SKU3', 'SKU4', 'SKU5'], 90),
    'picks': np.random.randint(10, 200, 90)
})

velocity_trends = optimizer.calculate_velocity_trend(historical_data)
print("Velocity Trends:")
print(velocity_trends)

current_slotting = {
    'SKU1': 'Golden Zone',
    'SKU2': 'Forward Pick',
    'SKU3': 'Reserve',
    'SKU4': 'C_Zone',
    'SKU5': 'Forward Pick'
}

recommendations = optimizer.recommend_reslotting(current_slotting, velocity_trends)
print("\nReslotting Recommendations:")
print(recommendations)
前置拣选库位容量规划
def calculate_forward_pick_size(sku_data, replenishment_frequency='daily'):
    """
    Determine optimal forward pick location size for each SKU

    Balance between:
    - Minimizing forward pick space (expensive)
    - Minimizing replenishment trips (labor cost)

    Parameters:
    -----------
    sku_data : DataFrame with columns
        - sku_id
        - daily_picks (average)
        - units_per_pick
        - cube_per_unit
        - replenishment_cost ($ per trip)

    Returns:
    --------
    Recommended forward pick capacity for each SKU
    """

    df = sku_data.copy()

    # Calculate daily cube picked
    df['daily_cube_picked'] = (df['daily_picks'] *
                                df['units_per_pick'] *
                                df['cube_per_unit'])

    # Determine replenishment period
    replen_days = {
        'daily': 1,
        'every_2_days': 2,
        'weekly': 7
    }
    days = replen_days.get(replenishment_frequency, 1)

    # Forward pick size = days of inventory
    df['forward_pick_cube'] = df['daily_cube_picked'] * days * 1.2  # 20% buffer

    # Round up to standard bin sizes
    standard_sizes = [1, 2, 4, 8, 16, 32]  # cubic feet

    def round_to_standard(size):
        for std_size in standard_sizes:
            if size <= std_size:
                return std_size
        return standard_sizes[-1]

    df['recommended_bin_size'] = df['forward_pick_cube'].apply(round_to_standard)

    # Calculate annual replenishment trips
    df['annual_replen_trips'] = (365 / days) * (df['recommended_bin_size'] > 0)
    df['annual_replen_cost'] = df['annual_replen_trips'] * df['replenishment_cost']

    # Calculate space cost (assuming $10/sq ft/year, 8 ft ceiling)
    space_cost_per_cuft = 10 / 8
    df['annual_space_cost'] = df['recommended_bin_size'] * space_cost_per_cuft

    df['total_annual_cost'] = df['annual_replen_cost'] + df['annual_space_cost']

    return df[[
        'sku_id', 'daily_picks', 'daily_cube_picked',
        'forward_pick_cube', 'recommended_bin_size',
        'annual_replen_trips', 'total_annual_cost'
    ]]


# Example
sku_data = pd.DataFrame({
    'sku_id': [f'SKU{i}' for i in range(1, 11)],
    'daily_picks': np.random.randint(5, 100, 10),
    'units_per_pick': np.random.uniform(1, 3, 10),
    'cube_per_unit': np.random.uniform(0.1, 2, 10),
    'replenishment_cost': np.full(10, 5.0)  # $5 per replenishment
})

forward_pick_sizing = calculate_forward_pick_size(sku_data, 'every_2_days')
print("Forward Pick Location Sizing:")
print(forward_pick_sizing)

工具与库

库位规划软件

商业解决方案:

  • Manhattan WMS:高级库位规划模块
  • Blue Yonder (JDA) WMS:AI 驱动的库位规划
  • SAP EWM:扩展仓库管理库位规划
  • HighJump WMS:基于任务的库位优化
  • Körber Supply Chain:动态库位规划
  • Infor WMS:基于周转速度的库位规划

专业库位规划工具:

  • EasySlot:独立的库位优化工具
  • SlotSmart:基于云的库位规划
  • Warehousing Efficiency Solutions:库位规划咨询 + 软件
Python 库
# Optimization
from pulp import *  # Linear programming
from scipy.optimize import linear_sum_assignment  # Hungarian algorithm
from ortools.linear_solver import pywraplp  # Google OR-Tools

# Data Analysis
import pandas as pd
import numpy as np
from sklearn.cluster import KMeans  # Clustering for product affinity
from scipy.spatial.distance import cdist  # Distance calculations

# Visualization
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px  # Interactive warehouse heatmaps

常见挑战与解决方案

挑战:季节性需求变化

问题:

  • 夏季和冬季产品的周转速度不同
  • 节假日季会大幅改变拣选模式
  • 开学季需求激增

解决方案:

  • 设置季节性库位规划配置(每年 3-4 套)
  • 使用滚动 30 天周转速度进行分类
  • 为季节性商品预留灵活区域
  • 在季节开始前提前规划库位(主动应对)
  • 使用前置拣选区溢出区域
  • 在过渡期间每周监控周转速度
挑战:产品种类过多

问题:

  • 可用前置拣选货位不足以容纳过多 SKU
  • 不断有新 SKU 加入
  • 存在大量低周转长尾商品

解决方案:

  • 收紧 ABC 分类阈值(仅将排名前 15-20% 的商品放在前置拣选区)
  • 对 C 类商品实施从储备区逐件拣选
  • 整合相似 SKU
  • 淘汰低周转商品
  • 使用动态货位规划(持续重新优化)
  • 小件商品采用一货位多 SKU
挑战:物理限制

问题:

  • 上层货架有重量限制
  • 产品之间不兼容(危险品、温度要求)
  • 超大件商品无法放入标准货位

解决方案:

  • 在优化模型中加入重量限制
  • 按产品类型预先划分区域
  • 设置专用的大件拣选区
  • 重型/大型商品采用地面堆放
  • 使用专用设备区域
  • 储备区采用多深位托盘货架
挑战:补货造成的干扰

问题:

  • 拣选波次期间补货拥堵
  • 叉车通行干扰拣选人员
  • 前置拣选库存耗尽

解决方案:

  • 安排在拣选波次之间补货
  • 使用独立的补货通道
  • 实施最小/最大库存补货触发机制
  • 合理设置前置拣选货位大小
  • 采用双深位前置拣选(A/B 位置)
  • 自动补货(AS/RS、穿梭车)
挑战:变革阻力

问题:

  • 拣选人员抵触新的货位规划
  • 肌肉记忆受到影响
  • 短期生产力下降

解决方案:

  • 逐步实施(逐个区域推进)
  • 清晰传达益处
  • 提供更新后的拣选面位图
  • 预留 2 周适应期
  • 使用语音/RF 引导拣选(提示货位)
  • 跟踪并庆祝改进成果
  • 邀请经验丰富的拣选人员参与设计
挑战:数据质量

问题:

  • 拣选历史记录不准确
  • 缺少产品尺寸数据
  • 新商品的产品周转速度未知

解决方案:

  • 清理历史数据(删除异常值、退货记录)
  • 对产品尺寸进行实地审核
  • 收货时进行体积扫描
  • 根据相似产品估算周转速度
  • 新 SKU 使用类别平均值
  • 先采用保守的货位规划,30 天后再调整

输出格式

库位优化报告

执行摘要:

  • 当前拣选效率:每小时 120 行
  • 预计提升后:每小时 145 行(+21%)
  • 分析的 SKU 总数:2,450
  • 需要移库的 SKU:347(14%)
  • 预计实施时间:3 周
  • 投资回报周期:6 个月

ABC 分析结果:

类别SKU 数量SKU 占比拣选占比月均拣选次数区域分配
A24510%75%850黄金区
B61225%20%180前置拣选区
C1,59365%5%12储备存储区

前 25 个 SKU——黄金区布局:

SKU描述月拣选次数COI当前库位最优库位行走距离节省
SKU1234Widget A1,2450.05C-15-3A-01-23,850 英尺/月
SKU2345Gadget B1,1230.08B-08-1A-01-33,200 英尺/月
.....................

产品关联集群:

Cluster 1 (Office Supplies): 45 SKUs
  - Frequently ordered together
  - Recommend: Aisle A1-A2

Cluster 2 (Electronics): 32 SKUs
  - High correlation in order patterns
  - Recommend: Aisle B1-B2

...

实施计划:

第 1 阶段 - 第 1 周: 黄金区(A 类商品)

  • 将前 50 个 SKU 移至最优库位
  • 预计实现:总收益的 50%
  • 尽量减少干扰

第 2 阶段 - 第 2 周: 前置拣选区(B 类商品)

  • 优化 200 个 SKU 的库位
  • 预计额外实现:30% 的收益

第 3 阶段 - 第 3 周: 储备存储区(C 类商品)

  • 集中存储并整理
  • 预计额外实现:20% 的收益

预期收益:

指标当前优化后改善幅度
平均拣选行走距离(英尺)2,8502,100-26%
每小时拣选次数120145+21%
每日人工工时180150-17%
年人工成本$540K$450K-$90K
前置拣选区利用率68%89%+21 个百分点

维护计划:

  • 每周监控周转速度
  • 每月重新进行 ABC 分类
  • 每季度全面审查库位规划
  • 季节性调整商品配置(每年 4 次)
  • 持续改进(每周调整 1% 的库位)

要询问的问题

如需更多背景信息:

  1. 您有多少个 SKU 和存储库位?
  2. 当前的拣选效率是多少(行/小时)?
  3. 您是否设有前置拣选区和储备区?
  4. 您目前多久重新规划一次库位?
  5. 您使用什么 WMS 或库位规划软件?
  6. 是否存在季节性需求模式?
  7. 有哪些物理限制(重量、温度、危险品)?
  8. 目前如何安排补货?

相关技能

  • picker-routing-optimization:用于根据库位规划优化拣选路径
  • warehouse-design:用于整体布局和区域设计
  • order-batching-optimization:用于将订单分批,以便一起拣选
  • wave-planning-optimization:用于规划拣选波次
  • task-assignment-problem:用于将拣选员分配到各区域
  • inventory-optimization:用于确定前置拣选区的库存水平
  • demand-forecasting:用于预测未来周转速度变化
  • abc-analysis:用于产品分类

评论

支持 Markdown