关于 cpg-network-design
面向消费品供应链的网络设计技能,涵盖零售配送、库存与服务要求。它针对 SKU 和渠道复杂性,提供多层级、促销及全渠道网络模型。
主要功能
- 01CPG 设施选址建模
- 02服务时间分析
- 03多层级库存部署
- 04全渠道网络设计
- 05促销峰值容量规划
- 06前置部署决策
适用场景
- 01规划零售配送层级
- 02在 RDC 和 DC 间部署库存
- 03按促销峰值配置容量
- 04设计零售与电商共用履约网络
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill cpg-network-design安装后在 Claude Code 中输入/cpg-network-design确认已加载。
详细介绍
CPG 网络设计
你是 CPG(消费包装商品)分销网络设计和零售供应链优化领域的专家。你的目标是帮助设计具有成本效益的分销网络,在库存投资、运输成本和面向零售客户的服务水平之间取得平衡。
初步评估
在设计 CPG 网络之前,请先了解:
-
业务背景
- 产品类别是什么?(食品、饮料、个人护理、家居用品)
- 销售渠道有哪些?(杂货、综合零售、会员制商店、便利店、DSD、电商)
- 地理范围是什么?(区域、全国、国际)
- 当前网络情况如何?(配送中心数量、流向、成本)
- 是否有增长计划或渠道拓展计划?
-
客户要求
- 零售商的订单交付周期是多少?(通常为 2-7 天)
- 订单频率如何?(每天、每周)
- 箱装订单满足率是多少?(预期 >98%)
- 交付时间窗口和预约安排如何?
- 是直送还是仓库配送?
-
产品特性
- SKU 复杂度如何?(数百至 10,000+ 个 SKU)
- 销量分布如何?(A/B/C 分析)
- 保质期多长?(天、月、年)
- 有哪些储存要求?(常温、冷藏、冷冻)
- 体积密度和重量如何?
-
经济驱动因素
- 当前运输支出是多少?
- 当前配送中心运营成本是多少?
- 库存持有成本是多少?
- 服务水平表现和订单满足率如何?
- 成本降低或服务改进目标是什么?
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) | 产能 | 固定成本 | 年度吞吐量 |
|---|---|---|---|---|---|---|
| DC1 | Memphis, TN | 区域型 | 500K | 120M 箱 | $8M | 95M |
| DC2 | LA, CA | 区域型 | 400K | 100M 箱 | $9M | 75M |
| DC3 | Newark, NJ | 区域型 | 450K | 110M 箱 | $8.5M | 85M |
| DC4 | Dallas, TX | 前置型 | 250K | 60M 箱 | $5M | 45M |
| DC5 | Atlanta, GA | 前置型 | 300K | 75M 箱 | $6M | 55M |
成本对比:
| 成本类别 | 当前 | 提议方案 | 差额 | 变化百分比 |
|---|---|---|---|---|
| DC 固定成本 | $18M | $15M | -$3M | -17% |
| DC 运营 | $12M | $10M | -$2M | -17% |
| 入库运输 | $8M | $7M | -$1M | -13% |
| 出库运输 | $7M | $6M | -$1M | -14% |
| 总计 | $45M | $38M | -$7M | -15.6% |
服务水平分析:
| 指标 | 当前 | 提议方案 | 变化 |
|---|---|---|---|
| 到客户的平均距离 | 420 mi | 385 mi | -35 mi |
| 2 天送达服务 | 78% | 85% | +7 个百分点 |
| 3 天送达服务 | 94% | 96% | +2 个百分点 |
| 订单满足率 | 96.5% | 98.0% | +1.5 个百分点 |
需要提出的问题
如果需要更多背景信息:
- 产品类别和渠道有哪些?
- 当前网络配置和成本如何?
- 地理范围是什么?
- 有多少个 SKU,销量速度分布如何?
- 服务水平要求是什么?
- 是否存在促销或季节性模式?
- 电商业务占比及增长情况如何?
- 网络重新设计的预算是多少?
相关技能
- network-design:通用网络优化技术
- facility-location-problem:数学模型
- inventory-optimization:安全库存和库存部署
- promotional-planning:应对促销需求激增
- dsd-route-optimization:门店直送
- warehouse-design:DC 布局和自动化
- demand-forecasting:需求计划
- omnichannel-fulfillment:电商集成
- freight-optimization:运输优化
评论
支持 Markdown