mcpField
C
cpg-network-design

cpg-network-design

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

关于 cpg-network-design

面向消费品供应链的网络设计技能,涵盖零售配送、库存与服务要求。它针对 SKU 和渠道复杂性,提供多层级、促销及全渠道网络模型。

主要功能

  1. 01CPG 设施选址建模
  2. 02服务时间分析
  3. 03多层级库存部署
  4. 04全渠道网络设计
  5. 05促销峰值容量规划
  6. 06前置部署决策

适用场景

  1. 01规划零售配送层级
  2. 02在 RDC 和 DC 间部署库存
  3. 03按促销峰值配置容量
  4. 04设计零售与电商共用履约网络

安装

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

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill cpg-network-design

安装后在 Claude Code 中输入/cpg-network-design确认已加载。

详细介绍

CPG 网络设计

你是 CPG(消费包装商品)分销网络设计和零售供应链优化领域的专家。你的目标是帮助设计具有成本效益的分销网络,在库存投资、运输成本和面向零售客户的服务水平之间取得平衡。

初步评估

在设计 CPG 网络之前,请先了解:

  1. 业务背景

    • 产品类别是什么?(食品、饮料、个人护理、家居用品)
    • 销售渠道有哪些?(杂货、综合零售、会员制商店、便利店、DSD、电商)
    • 地理范围是什么?(区域、全国、国际)
    • 当前网络情况如何?(配送中心数量、流向、成本)
    • 是否有增长计划或渠道拓展计划?
  2. 客户要求

    • 零售商的订单交付周期是多少?(通常为 2-7 天)
    • 订单频率如何?(每天、每周)
    • 箱装订单满足率是多少?(预期 >98%)
    • 交付时间窗口和预约安排如何?
    • 是直送还是仓库配送?
  3. 产品特性

    • SKU 复杂度如何?(数百至 10,000+ 个 SKU)
    • 销量分布如何?(A/B/C 分析)
    • 保质期多长?(天、月、年)
    • 有哪些储存要求?(常温、冷藏、冷冻)
    • 体积密度和重量如何?
  4. 经济驱动因素

    • 当前运输支出是多少?
    • 当前配送中心运营成本是多少?
    • 库存持有成本是多少?
    • 服务水平表现和订单满足率如何?
    • 成本降低或服务改进目标是什么?

CPG 网络设计框架

CPG 特有的网络特征

与一般工业品相比:

  • SKU 复杂度高(通常为 1,000-10,000+ 个 SKU)
  • 产品周转快(销量高)
  • 交付周期短(2-5 天)
  • 服务水平要求高(>98%)
  • 促销波动大(销量提升 30-50%)
  • 多渠道分销(零售、DSD、电商)
  • 利润率低(营业利润率为 2-5%)

网络层级选项:

1. 直送门店(工厂 → 门店)

  • 生鲜/DSD 产品(面包、牛奶、零食)
  • 高频次、小批量配送
  • 司机负责陈列
  • 请参阅 dsd-route-optimization

2. 单层网络(工厂 → 配送中心 → 门店)

  • 最常见于常温保存产品
  • 区域配送中心(通常为 3-6 个)
  • 每个配送中心供应全线产品
  • 经济订货量

3. 两层网络(工厂 → RDC → 前置配送中心 → 门店)

  • 覆盖范围广的全国性品牌
  • RDC 用于慢销品(1-2 个大型设施)
  • 前置配送中心用于快销品(10-15 个较小设施)
  • 跨层级优化库存

4. 混合模式(多种策略)

  • 快销品:由前置配送中心配送
  • 慢销品:由 RDC 直接发货
  • 促销品:专门备货
  • 电商:使用专用履约中心

网络设计模型

CPG 设施选址模型
from pulp import *
import pandas as pd
import numpy as np
from scipy.spatial.distance import cdist

class CPGNetworkOptimizer:
    """
    CPG-specific network design optimization
    """

    def __init__(self, customers_df, potential_dcs_df, plants_df):
        """
        Initialize CPG network optimizer

        Parameters:
        - customers_df: retailers with demand ['customer_id', 'lat', 'lon',
                       'demand_cases', 'service_level_days']
        - potential_dcs_df: potential DC locations ['dc_id', 'lat', 'lon',
                           'fixed_cost', 'variable_cost_per_case', 'capacity']
        - plants_df: manufacturing plants ['plant_id', 'lat', 'lon', 'capacity']
        """
        self.customers = customers_df
        self.dcs = potential_dcs_df
        self.plants = plants_df

        # Calculate distance matrices
        self.dc_to_customer_dist = self._calc_distances(
            self.dcs[['lat', 'lon']],
            self.customers[['lat', 'lon']]
        )

        self.plant_to_dc_dist = self._calc_distances(
            self.plants[['lat', 'lon']],
            self.dcs[['lat', 'lon']]
        )

    def _calc_distances(self, from_coords, to_coords):
        """Calculate distance matrix (miles)"""
        distances = cdist(from_coords.values, to_coords.values, metric='euclidean')
        return distances * 69  # Convert degrees to miles (approximate)

    def optimize_network(self, max_dcs=None, service_distance=None,
                          transport_rate_tl=2.50, transport_rate_ltl=25.0):
        """
        Optimize CPG distribution network

        Parameters:
        - max_dcs: maximum number of DCs to open (None = no limit)
        - service_distance: max miles for service level (None = no constraint)
        - transport_rate_tl: truckload rate ($/mile)
        - transport_rate_ltl: LTL rate ($/cwt)

        Returns:
        - optimal network configuration
        """

        prob = LpProblem("CPG_Network", LpMinimize)

        # Decision variables
        C = range(len(self.customers))
        D = range(len(self.dcs))
        P = range(len(self.plants))

        # y[d] = 1 if DC d is opened
        y = LpVariable.dicts("DC_Open", D, cat='Binary')

        # x[c,d] = flow from DC d to customer c (cases)
        x = LpVariable.dicts("Customer_Flow",
                              [(c,d) for c in C for d in D],
                              lowBound=0)

        # z[p,d] = flow from plant p to DC d (cases)
        z = LpVariable.dicts("DC_Flow",
                              [(p,d) for p in P for d in D],
                              lowBound=0)

        # Objective: Minimize total cost
        objective = 0

        # Fixed DC costs
        for d in D:
            objective += self.dcs.iloc[d]['fixed_cost'] * y[d]

        # DC variable costs
        for c in C:
            for d in D:
                handling_cost = self.dcs.iloc[d]['variable_cost_per_case']
                objective += x[c,d] * handling_cost

        # Outbound transportation (DC → Customer)
        for c in C:
            for d in D:
                distance = self.dc_to_customer_dist[d,c]
                demand = self.customers.iloc[c]['demand_cases']

                # Simplified: LTL for <20,000 lbs, TL otherwise
                if demand < 400:  # 400 cases ~= 20,000 lbs
                    cost = distance * transport_rate_ltl / 100 * demand * 50 / 100  # $/cwt
                else:
                    cost = distance * transport_rate_tl

                objective += x[c,d] * cost / demand  # Cost per case

        # Inbound transportation (Plant → DC)
        for p in P:
            for d in D:
                distance = self.plant_to_dc_dist[p,d]
                # Assume truckload shipments to DCs
                cost_per_mile = transport_rate_tl
                objective += z[p,d] * distance * cost_per_mile / 1000  # Per case

        prob += objective

        # Constraints

        # 1. Each customer served by exactly one DC
        for c in C:
            prob += lpSum([x[c,d] for d in D]) >= self.customers.iloc[c]['demand_cases']

        # 2. DC capacity constraints
        for d in D:
            prob += lpSum([x[c,d] for c in C]) <= \
                    self.dcs.iloc[d]['capacity'] * y[d]

        # 3. DC flow balance (inbound = outbound)
        for d in D:
            inbound = lpSum([z[p,d] for p in P])
            outbound = lpSum([x[c,d] for c in C])
            prob += inbound >= outbound

        # 4. Plant capacity constraints
        for p in P:
            prob += lpSum([z[p,d] for d in D]) <= self.plants.iloc[p]['capacity']

        # 5. Service distance constraint (optional)
        if service_distance:
            for c in C:
                for d in D:
                    if self.dc_to_customer_dist[d,c] > service_distance:
                        prob += x[c,d] == 0

        # 6. Maximum number of DCs (optional)
        if max_dcs:
            prob += lpSum([y[d] for d in D]) <= max_dcs

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

        return self._extract_solution(prob, x, y, z, C, D, P)

    def _extract_solution(self, prob, x, y, z, C, D, P):
        """Extract solution details"""

        if prob.status != 1:
            return {'status': 'infeasible'}

        # Open DCs
        open_dcs = [
            {
                'dc_id': self.dcs.iloc[d]['dc_id'],
                'location': f"{self.dcs.iloc[d]['lat']:.2f}, {self.dcs.iloc[d]['lon']:.2f}",
                'fixed_cost': self.dcs.iloc[d]['fixed_cost'],
                'capacity': self.dcs.iloc[d]['capacity']
            }
            for d in D if y[d].varValue > 0.5
        ]

        # Customer assignments
        assignments = []
        for c in C:
            for d in D:
                if x[c,d].varValue > 0.01:
                    assignments.append({
                        'customer': self.customers.iloc[c]['customer_id'],
                        'dc': self.dcs.iloc[d]['dc_id'],
                        'flow': x[c,d].varValue,
                        'distance': self.dc_to_customer_dist[d,c]
                    })

        # Calculate metrics
        total_flow = sum(a['flow'] for a in assignments)
        weighted_distance = sum(a['flow'] * a['distance'] for a in assignments)
        avg_distance = weighted_distance / total_flow if total_flow > 0 else 0

        return {
            'status': 'optimal',
            'total_cost': value(prob.objective),
            'num_dcs': len(open_dcs),
            'open_dcs': open_dcs,
            'assignments': pd.DataFrame(assignments),
            'avg_distance_to_customer': avg_distance,
            'total_cases': total_flow
        }


# Example usage
customers = pd.DataFrame({
    'customer_id': ['Retailer_A', 'Retailer_B', 'Retailer_C', 'Retailer_D'],
    'lat': [34.05, 41.88, 39.74, 29.76],
    'lon': [-118.24, -87.63, -104.99, -95.37],
    'demand_cases': [50000, 75000, 40000, 60000],
    'service_level_days': [3, 3, 3, 3]
})

potential_dcs = pd.DataFrame({
    'dc_id': ['DC_West', 'DC_Central', 'DC_South', 'DC_East'],
    'lat': [36.17, 39.10, 33.75, 40.71],
    'lon': [-115.14, -94.58, -84.39, -74.01],
    'fixed_cost': [2000000, 1800000, 1900000, 2500000],
    'variable_cost_per_case': [1.50, 1.35, 1.40, 1.60],
    'capacity': [150000, 200000, 150000, 180000]
})

plants = pd.DataFrame({
    'plant_id': ['Plant_1', 'Plant_2'],
    'lat': [41.50, 34.00],
    'lon': [-90.00, -118.00],
    'capacity': [300000, 250000]
})

optimizer = CPGNetworkOptimizer(customers, potential_dcs, plants)
result = optimizer.optimize_network(max_dcs=3, service_distance=500)

print(f"Status: {result['status']}")
print(f"Total Cost: ${result['total_cost']:,.0f}")
print(f"Number of DCs: {result['num_dcs']}")
print(f"Average Distance: {result['avg_distance_to_customer']:.0f} miles")

服务水平建模

市场覆盖时间分析
def calculate_days_to_market(dc_locations, customer_locations,
                              production_lead_time=3, dc_processing_days=1):
    """
    Calculate end-to-end days from production to customer delivery

    Parameters:
    - dc_locations: DC coordinates
    - customer_locations: customer coordinates
    - production_lead_time: days to produce
    - dc_processing_days: days for DC receiving/putaway

    Returns:
    - service time analysis
    """

    from scipy.spatial.distance import cdist

    # Calculate distances
    distances = cdist(
        dc_locations[['lat', 'lon']].values,
        customer_locations[['lat', 'lon']].values,
        metric='euclidean'
    ) * 69  # miles

    # Transportation time (miles → days)
    # Assume: <250 miles = 1 day, 250-500 = 2 days, 500-750 = 3 days, etc.
    transport_days = np.ceil(distances / 250)

    # Total days to market
    total_days = production_lead_time + dc_processing_days + transport_days

    # Service level metrics
    service_metrics = {
        '1_day_service_pct': np.mean(transport_days <= 1) * 100,
        '2_day_service_pct': np.mean(transport_days <= 2) * 100,
        '3_day_service_pct': np.mean(transport_days <= 3) * 100,
        'avg_transport_days': np.mean(transport_days),
        'avg_total_days': np.mean(total_days),
        'max_days_to_market': np.max(total_days)
    }

    return service_metrics


# Benchmark service targets for CPG
service_targets = {
    'Grocery/Mass': {
        'order_lead_time_days': 3,
        'fill_rate_target': 0.98,
        'on_time_delivery_target': 0.95
    },
    'Club': {
        'order_lead_time_days': 5,
        'fill_rate_target': 0.99,
        'on_time_delivery_target': 0.98
    },
    'Convenience': {
        'order_lead_time_days': 2,
        'fill_rate_target': 0.95,
        'on_time_delivery_target': 0.90
    },
    'E-commerce': {
        'order_lead_time_days': 2,
        'fill_rate_target': 0.98,
        'on_time_delivery_target': 0.97
    }
}

跨网络库存优化

多层级库存模型
def optimize_inventory_deployment(sku_data, network_config, service_level=0.98):
    """
    Optimize safety stock across multi-echelon CPG network

    Parameters:
    - sku_data: SKU demand and variability
    - network_config: network structure (RDC, DCs, stores)
    - service_level: target service level

    Returns:
    - optimal inventory placement
    """

    from scipy.stats import norm

    z_score = norm.ppf(service_level)

    inventory_plan = []

    for sku in sku_data:
        # Demand parameters
        weekly_demand = sku['weekly_demand']
        demand_std = sku['demand_std']
        lead_time_weeks = sku['lead_time_weeks']

        # Safety stock calculation
        lead_time_demand = weekly_demand * lead_time_weeks
        lead_time_std = demand_std * np.sqrt(lead_time_weeks)

        safety_stock = z_score * lead_time_std

        # Cycle stock (replenishment quantity)
        order_frequency_weeks = sku.get('order_frequency_weeks', 2)
        cycle_stock = weekly_demand * order_frequency_weeks / 2

        # Total inventory
        total_inventory = safety_stock + cycle_stock

        inventory_plan.append({
            'sku': sku['sku_id'],
            'lead_time_demand': lead_time_demand,
            'safety_stock': safety_stock,
            'cycle_stock': cycle_stock,
            'total_inventory': total_inventory,
            'inventory_value': total_inventory * sku['unit_cost'],
            'service_level': service_level
        })

    return pd.DataFrame(inventory_plan)


# Network inventory pooling effect
def calculate_inventory_pooling_benefit(current_locations, new_locations,
                                         current_inventory):
    """
    Calculate inventory reduction from consolidation (Square Root Law)
    """

    pooling_factor = np.sqrt(new_locations / current_locations)
    new_inventory = current_inventory * pooling_factor
    inventory_reduction = current_inventory - new_inventory

    savings = {
        'current_locations': current_locations,
        'new_locations': new_locations,
        'current_inventory': current_inventory,
        'new_inventory': new_inventory,
        'inventory_reduction': inventory_reduction,
        'reduction_pct': inventory_reduction / current_inventory * 100
    }

    return savings


# Example: Consolidate 10 DCs to 5
pooling = calculate_inventory_pooling_benefit(
    current_locations=10,
    new_locations=5,
    current_inventory=10000000  # $10M inventory
)

print(f"Inventory Reduction: ${pooling['inventory_reduction']:,.0f}")
print(f"Reduction %: {pooling['reduction_pct']:.1f}%")

全渠道网络设计

混合履约网络
class OmnichannelNetworkDesigner:
    """
    Design omnichannel fulfillment network for CPG

    Channels:
    - Traditional retail (store replenishment)
    - E-commerce (direct-to-consumer)
    - Click & collect (buy online, pick up in store)
    """

    def __init__(self, retail_demand, ecom_demand):
        self.retail = retail_demand
        self.ecom = ecom_demand

    def design_hybrid_network(self, strategy='integrated'):
        """
        Design network based on strategy

        Strategies:
        - 'integrated': Shared DCs for retail and e-commerce
        - 'dedicated': Separate e-commerce fulfillment centers
        - 'hybrid': Regional DCs + dedicated e-com nodes
        """

        if strategy == 'integrated':
            return self._design_integrated()
        elif strategy == 'dedicated':
            return self._design_dedicated()
        elif strategy == 'hybrid':
            return self._design_hybrid()

    def _design_integrated(self):
        """Shared network for all channels"""

        total_demand = self.retail['demand'].sum() + self.ecom['demand'].sum()

        network = {
            'strategy': 'integrated',
            'dc_type': 'multi-channel',
            'num_dcs': self._optimal_dc_count(total_demand),
            'characteristics': {
                'retail_fulfillment': 'Case picking',
                'ecom_fulfillment': 'Each picking from same DC',
                'inventory': 'Shared pool',
                'pros': ['Inventory pooling', 'Lower fixed costs', 'Simpler'],
                'cons': ['Conflicting operations', 'Peak congestion', 'Less specialized']
            }
        }

        return network

    def _design_dedicated(self):
        """Separate networks for retail and e-commerce"""

        retail_dcs = self._optimal_dc_count(self.retail['demand'].sum())
        ecom_fcs = self._optimal_fc_count(self.ecom['demand'].sum())

        network = {
            'strategy': 'dedicated',
            'retail_dcs': retail_dcs,
            'ecom_fcs': ecom_fcs,
            'total_facilities': retail_dcs + ecom_fcs,
            'characteristics': {
                'retail_fulfillment': 'Case picking, pallet shipping',
                'ecom_fulfillment': 'Each picking, parcel shipping',
                'inventory': 'Separate pools',
                'pros': ['Optimized for each channel', 'No conflicts', 'Better service'],
                'cons': ['Higher fixed costs', 'Duplicate inventory', 'Complex']
            }
        }

        return network

    def _design_hybrid(self):
        """Hybrid: Regional DCs + dedicated e-commerce nodes"""

        network = {
            'strategy': 'hybrid',
            'regional_dcs': 3,  # Large regional DCs for retail + slow e-com
            'ecom_forward_nodes': 6,  # Smaller e-com nodes for fast movers
            'characteristics': {
                'fast_movers_ecom': 'From forward e-com nodes (2-day)',
                'slow_movers_ecom': 'From regional DCs (3-5 day)',
                'retail': 'From regional DCs',
                'inventory': 'Fast movers duplicated, slow movers pooled',
                'pros': ['Balance cost and service', 'Fast e-com fulfillment', 'Inventory optimization'],
                'cons': ['Moderate complexity', 'Requires smart routing']
            }
        }

        return network

    def _optimal_dc_count(self, demand):
        """Simple heuristic for DC count"""
        # Rule of thumb: 1 DC per $500M demand
        return max(2, int(demand / 500000000))

    def _optimal_fc_count(self, demand):
        """Optimal e-commerce FC count"""
        # E-commerce needs more nodes for fast delivery
        return max(3, int(demand / 200000000))


# Example
designer = OmnichannelNetworkDesigner(
    retail_demand=pd.DataFrame({'demand': [1000000000]}),  # $1B retail
    ecom_demand=pd.DataFrame({'demand': [200000000]})      # $200M e-commerce
)

integrated = designer.design_hybrid_network('integrated')
dedicated = designer.design_hybrid_network('dedicated')
hybrid = designer.design_hybrid_network('hybrid')

print("Integrated:", integrated)
print("\nDedicated:", dedicated)
print("\nHybrid:", hybrid)

CPG 特有考量

促销高峰产能
def size_network_for_promotions(base_demand, promotional_lift=0.40,
                                  promotional_weeks_per_year=12):
    """
    Size network capacity considering promotional surge

    CPG challenge: Promotions can increase demand 30-50%
    Network must handle surge without stockouts
    """

    # Annual base demand
    annual_base = base_demand * 52

    # Promotional demand
    promo_demand_per_week = base_demand * (1 + promotional_lift)
    promo_weeks = promotional_weeks_per_year
    base_weeks = 52 - promo_weeks

    # Total annual demand
    annual_total = (base_demand * base_weeks) + (promo_demand_per_week * promo_weeks)

    # Peak week capacity needed
    peak_capacity = promo_demand_per_week

    # Design capacity (with buffer)
    design_capacity = peak_capacity * 1.15  # 15% buffer

    capacity_plan = {
        'base_weekly_demand': base_demand,
        'peak_weekly_demand': peak_capacity,
        'design_capacity': design_capacity,
        'capacity_utilization_base': base_demand / design_capacity,
        'capacity_utilization_peak': peak_capacity / design_capacity,
        'annual_demand': annual_total,
        'promotional_weeks': promo_weeks,
        'avg_utilization': annual_total / (design_capacity * 52)
    }

    return capacity_plan


# Example
promo_capacity = size_network_for_promotions(
    base_demand=100000,  # cases/week
    promotional_lift=0.40,
    promotional_weeks_per_year=12
)

print(f"Design Capacity: {promo_capacity['design_capacity']:,.0f} cases/week")
print(f"Average Utilization: {promo_capacity['avg_utilization']:.1%}")
print(f"Peak Utilization: {promo_capacity['capacity_utilization_peak']:.1%}")
货位规划与前置部署
def optimize_forward_deployment(skus, dc_network, forward_deployment_cost):
    """
    Optimize which SKUs to forward-deploy to more DCs

    Trade-off:
    - Forward deploy fast movers: Lower transport, higher inventory
    - Centralize slow movers: Higher transport, lower inventory
    """

    sku_strategy = []

    for sku in skus:
        velocity = sku['annual_demand']
        unit_value = sku['unit_cost']

        # Transport cost savings from forward deployment
        transport_savings = calculate_transport_savings(sku, dc_network)

        # Inventory cost increase from duplication
        inventory_increase = (dc_network['num_forward_dcs'] - 1) * \
                            sku['safety_stock'] * unit_value * 0.25  # 25% carrying cost

        # Net benefit
        net_benefit = transport_savings - inventory_increase

        # Decision
        if net_benefit > 0:
            strategy = 'forward_deploy'
            locations = dc_network['num_forward_dcs']
        else:
            strategy = 'centralize'
            locations = 1  # Keep at RDC only

        sku_strategy.append({
            'sku': sku['sku_id'],
            'strategy': strategy,
            'locations': locations,
            'transport_savings': transport_savings,
            'inventory_increase': inventory_increase,
            'net_benefit': net_benefit
        })

    return pd.DataFrame(sku_strategy)


def calculate_transport_savings(sku, dc_network):
    """Calculate transport savings from forward deployment"""

    # Simplified model
    central_distance = dc_network['avg_distance_from_rdc']
    forward_distance = dc_network['avg_distance_from_forward_dc']

    distance_savings = central_distance - forward_distance

    # Annual shipments
    shipments_per_year = sku['annual_demand'] / sku['order_size']

    # Cost per shipment
    cost_per_mile = 2.50
    cost_savings = shipments_per_year * distance_savings * cost_per_mile

    return cost_savings

绩效指标

CPG 网络 KPI
class CPGNetworkMetrics:
    """
    Track CPG network performance metrics
    """

    def __init__(self, network_data):
        self.data = network_data

    def calculate_kpis(self):
        """Calculate comprehensive network KPIs"""

        kpis = {}

        # Cost metrics
        kpis['total_network_cost'] = self._total_network_cost()
        kpis['cost_per_case'] = kpis['total_network_cost'] / self.data['total_cases']

        # Cost breakdown
        kpis['cost_breakdown'] = {
            'fixed_dc_costs': self._fixed_dc_costs(),
            'variable_dc_costs': self._variable_dc_costs(),
            'inbound_transport': self._inbound_transport_cost(),
            'outbound_transport': self._outbound_transport_cost(),
            'inventory_carrying': self._inventory_carrying_cost()
        }

        # Service metrics
        kpis['avg_distance_to_customer'] = self._avg_distance()
        kpis['2_day_service_pct'] = self._service_coverage(max_distance=500)
        kpis['3_day_service_pct'] = self._service_coverage(max_distance=750)

        # Efficiency metrics
        kpis['dc_utilization'] = self._dc_utilization()
        kpis['cases_per_dc'] = self.data['total_cases'] / self.data['num_dcs']

        # Inventory metrics
        kpis['total_inventory_value'] = self._total_inventory()
        kpis['inventory_turns'] = self._inventory_turns()
        kpis['days_of_supply'] = 365 / kpis['inventory_turns']

        return kpis

    def _total_network_cost(self):
        """Total annual network cost"""
        return sum(self.data['cost_breakdown'].values())

    def _fixed_dc_costs(self):
        """Total fixed DC costs"""
        return sum(dc['fixed_cost'] for dc in self.data['dcs'])

    def _variable_dc_costs(self):
        """Total variable DC handling costs"""
        return self.data['total_cases'] * self.data['avg_variable_cost_per_case']

    def _inbound_transport_cost(self):
        """Plant to DC transportation"""
        return self.data.get('inbound_transport_cost', 0)

    def _outbound_transport_cost(self):
        """DC to customer transportation"""
        return self.data.get('outbound_transport_cost', 0)

    def _inventory_carrying_cost(self):
        """Inventory carrying cost (25% of inventory value)"""
        return self._total_inventory() * 0.25

    def _avg_distance(self):
        """Average distance from DC to customer"""
        return self.data['weighted_avg_distance']

    def _service_coverage(self, max_distance):
        """% customers within distance"""
        return self.data['service_coverage'].get(max_distance, 0)

    def _dc_utilization(self):
        """Average DC capacity utilization"""
        return self.data['total_cases'] / sum(dc['capacity'] for dc in self.data['dcs'])

    def _total_inventory(self):
        """Total inventory value in network"""
        return self.data.get('total_inventory_value', 0)

    def _inventory_turns(self):
        """Inventory turnover ratio"""
        annual_cogs = self.data.get('annual_cogs', 0)
        avg_inventory = self._total_inventory()
        return annual_cogs / avg_inventory if avg_inventory > 0 else 0


# CPG Network Benchmarks
cpg_benchmarks = {
    'cost_per_case': {
        'best_in_class': 2.50,
        'average': 3.50,
        'poor': 5.00
    },
    'inventory_turns': {
        'best_in_class': 12,
        'average': 8,
        'poor': 6
    },
    'fill_rate': {
        'best_in_class': 0.99,
        'average': 0.96,
        'poor': 0.92
    },
    'on_time_delivery': {
        'best_in_class': 0.98,
        'average': 0.95,
        'poor': 0.90
    }
}

工具与技术

CPG 网络设计软件

商业软件:

  • Coupa Supply Chain Design:面向 CPG 的网络优化
  • LLamasoft (Coupa):Supply Chain Guru
  • Blue Yonder Network Design:源自 JDA
  • Optilogic Cosmic Frog:基于云的网络设计
  • AIMMS:网络优化平台
  • Llamasoft Guru:情景规划

分析平台:

  • Kinaxis RapidResponse:S&OP 和网络规划
  • o9 Solutions:数字化规划平台
  • Anaplan:带优化功能的云端规划
  • SAP IBP:集成业务规划
Python 库
# Network optimization
from pulp import *
import pyomo.environ as pyo

# Geospatial analysis
import geopandas as gpd
from shapely.geometry import Point
from scipy.spatial import distance_matrix

# Optimization solvers
from ortools.linear_solver import pywraplp
import gurobipy as gp

# Data analysis
import pandas as pd
import numpy as np

# Visualization
import plotly.express as px
import folium
import matplotlib.pyplot as plt

常见挑战与解决方案

挑战:SKU 复杂度高

问题:

  • 需要管理 5,000-10,000 个 SKU
  • 滞销品造成库存负担
  • 前置部署成本高昂

解决方案:

  • 进行 ABC/XYZ 分类
  • 仅前置部署 A 类商品(前 20%)
  • 从中心地点配送 B/C 类商品
  • 对极慢销商品采用直发模式
  • 实施 SKU 合理化计划
挑战:促销波动性

问题:

  • 基础需求为 100K,促销需求为 150K
  • 需要应对需求激增的能力
  • 面临缺货或库存过时的风险

解决方案:

  • 按峰值需求规划产能(并预留缓冲)
  • 将促销库存前置备货
  • 使用灵活的 3PL 应对激增需求
  • 进行需求感知和快速补货
  • 与零售商协商产能
挑战:全渠道复杂性

问题:

  • 零售渠道需要整箱,电商需要单件
  • 服务水平各不相同
  • 库存分配存在冲突

解决方案:

  • 采用混合网络(共享 + 专用)
  • 通过门店发货满足电商订单
  • 为两种模式规划 DC 库位
  • 采用高级 ATP(可承诺量)
  • 实现统一的库存可视化
挑战:利润率低

问题:

  • CPG 利润率为 2-5%
  • 网络成本占销售额的 3-5%
  • 每一美元都至关重要

解决方案:

  • 持续优化(每季度审查)
  • 与同行进行基准比较
  • 每年协商运输费率
  • 在 DC 中实现自动化(人工成本占 DC 成本的 50%)
  • 实施库存削减计划

输出格式

CPG 网络设计报告

执行摘要:

  • 当前网络:8 个 DC,年度成本 $45M
  • 推荐网络:5 个 DC,年度成本 $38M
  • 年度节省:$7M(15.6%)
  • 服务水平:维持在 98%
  • 实施周期:18 个月

网络配置:

DC位置类型面积 (SF)产能固定成本年度吞吐量
DC1Memphis, TN区域型500K120M 箱$8M95M
DC2LA, CA区域型400K100M 箱$9M75M
DC3Newark, NJ区域型450K110M 箱$8.5M85M
DC4Dallas, TX前置型250K60M 箱$5M45M
DC5Atlanta, GA前置型300K75M 箱$6M55M

成本对比:

成本类别当前提议方案差额变化百分比
DC 固定成本$18M$15M-$3M-17%
DC 运营$12M$10M-$2M-17%
入库运输$8M$7M-$1M-13%
出库运输$7M$6M-$1M-14%
总计$45M$38M-$7M-15.6%

服务水平分析:

指标当前提议方案变化
到客户的平均距离420 mi385 mi-35 mi
2 天送达服务78%85%+7 个百分点
3 天送达服务94%96%+2 个百分点
订单满足率96.5%98.0%+1.5 个百分点

需要提出的问题

如果需要更多背景信息:

  1. 产品类别和渠道有哪些?
  2. 当前网络配置和成本如何?
  3. 地理范围是什么?
  4. 有多少个 SKU,销量速度分布如何?
  5. 服务水平要求是什么?
  6. 是否存在促销或季节性模式?
  7. 电商业务占比及增长情况如何?
  8. 网络重新设计的预算是多少?

相关技能

  • network-design:通用网络优化技术
  • facility-location-problem:数学模型
  • inventory-optimization:安全库存和库存部署
  • promotional-planning:应对促销需求激增
  • dsd-route-optimization:门店直送
  • warehouse-design:DC 布局和自动化
  • demand-forecasting:需求计划
  • omnichannel-fulfillment:电商集成
  • freight-optimization:运输优化

评论

支持 Markdown