cpg-network-design について
消費財サプライチェーン向けのネットワーク設計スキルで、小売配送、在庫、サービス要件を扱います。SKUやチャネルの複雑さに対応し、多段階・販促・オムニチャネルのモデルを扱います。
主な機能
- 01CPG施設立地モデル
- 02サービス所要時間の分析
- 03多段階在庫配置
- 04オムニチャネルネットワーク設計
- 05販促ピーク時の容量計画
- 06前方配置の意思決定
活用シーン
- 01小売配送の階層計画
- 02RDCとDC間の在庫配置
- 03販促ピークに向けた容量設定
- 04小売とEコマース共用のフルフィルメント設計
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill cpg-network-designインストール後、Claude Code で/cpg-network-designを入力して読み込まれたか確認します。
詳細
CPG ネットワーク設計
CPG(消費者向け包装商品)の流通ネットワーク設計と、小売サプライチェーンの最適化に関する専門知識を持っています。目標は、在庫投資、輸送コスト、小売顧客へのサービスレベルのバランスを取り、費用対効果の高い流通ネットワークの設計を支援することです。
初期評価
CPG ネットワークを設計する前に、次の点を把握してください。
-
ビジネスの背景
- 商品カテゴリーは何ですか?(食品、飲料、パーソナルケア用品、家庭用品)
- 販売チャネルは何ですか?(食料品店、量販店、会員制倉庫型店舗、コンビニエンスストア、DSD、eコマース)
- 地理的な対象範囲はどの程度ですか?(地域、国内、国際)
- 現在のネットワークはどのようなものですか?(DC の数、フローのパターン、コスト)
- 成長計画やチャネル拡大の予定はありますか?
-
顧客要件
- 小売業者の注文リードタイムはどのくらいですか?(通常2~7日)
- 注文頻度はどのくらいですか?(毎日、毎週)
- ケース充足率はどのくらいですか?(98%超が期待されます)
- 配送時間帯と配送予約の要件はありますか?
- 直送と倉庫経由の配送のどちらですか?
-
商品特性
- SKU の複雑さはどの程度ですか?(数百から10,000超の SKU)
- 出荷速度の分布はどのようになっていますか?(A/B/C 分析)
- 賞味・消費期限はどのくらいですか?(日、月、年)
- 保管要件は何ですか?(常温、冷蔵、冷凍)
- 容積密度と重量はどの程度ですか?
-
経済性の要因
- 現在の輸送費はいくらですか?
- 現在の DC 運営コストはいくらですか?
- 在庫保有コストはいくらですか?
- サービスレベルの実績と充足率はどうなっていますか?
- コスト削減またはサービス向上の目標は何ですか?
CPG ネットワーク設計フレームワーク
CPG 固有のネットワーク特性
一般的な工業製品との比較:
- SKU が非常に複雑(通常1,000~10,000超の SKU)
- 商品の回転が速い(出荷速度が高い)
- リードタイムが短い(2~5日)
- 高いサービスレベルが求められる(98%超)
- 販促による需要変動がある(30~50%増)
- マルチチャネルでの流通(小売、DSD、eコマース)
- 利益率が低い(営業利益率2~5%)
ネットワークの階層構成の選択肢:
1. 店舗直送(工場 → 店舗)
- 生鮮品/DSD 商品(パン、牛乳、スナック)
- 高頻度、小口配送
- ドライバーによる商品陳列
- dsd-route-optimization を参照
2. 単層(工場 → DC → 店舗)
- 常温保存可能な商品の大半で一般的
- 地域 DC(通常3~6か所)
- 各 DC で商品ライン全体を取り扱う
- 経済的発注量
3. 二層(工場 → RDC → 前方 DC → 店舗)
- 広範囲をカバーする全国ブランド
- RDC で低回転商品を扱う(大規模拠点1~2か所)
- 前方 DC で高回転商品を扱う(小規模拠点10~15か所)
- 階層間で在庫を最適化
4. ハイブリッド(複数の戦略)
- 高回転商品:前方 DC
- 低回転商品:RDC から直送
- 販促商品:特別な在庫構築
- eコマース:専用フルフィルメントセンター
ネットワーク設計モデル
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 の複雑性が高い
問題:
- 管理対象の SKU が 5,000~10,000 個
- 動きの遅い商品が在庫負担を生む
- 前方配置にはコストがかかる
解決策:
- ABC/XYZ 分析によるセグメンテーション
- A 商品(上位 20%)のみを前方配置する
- B/C 商品は中央拠点から出荷する
- 動きが非常に遅い商品にはドロップシッピングを利用する
- SKU の適正化プログラムを実施する
課題:販促による需要変動
問題:
- 通常需要は 100K、販促時の需要は 150K
- 需要急増に対応できる能力が必要
- 欠品や陳腐化のリスクがある
解決策:
- 余裕を持たせてピーク需要に合わせた能力を確保する
- 販促用在庫を前方配置する
- 需要急増には柔軟な 3PL を活用する
- 需要検知と迅速な補充を行う
- 小売業者と処理能力を交渉する
課題:オムニチャネルの複雑性
問題:
- 小売向けにはケース単位、e コマース向けには個品単位での対応が必要
- サービスレベルが異なる
- 在庫配分で競合が発生する
解決策:
- ハイブリッドネットワーク(共用+専用)を構築する
- e コマース向けに店舗から出荷する
- どちらの方式にも対応できるよう DC のスロッティングを行う
- 高度な ATP(納期回答可能在庫)を活用する
- 在庫の可視性を統合する
課題:低い利益率
問題:
- CPG の利益率は 2~5%
- ネットワークコストは売上の 3~5%
- 1 ドルも無駄にできない
解決策:
- 継続的に最適化する(四半期ごとに見直す)
- 同業他社とベンチマークする
- 輸送運賃を毎年交渉する
- DC で自動化を導入する(人件費は DC コストの 50%)
- 在庫削減プログラムを実施する
出力形式
CPG ネットワーク設計レポート
エグゼクティブサマリー:
- 現在のネットワーク:DC 8 か所、年間コスト $45M
- 推奨ネットワーク:DC 5 か所、年間コスト $38M
- 年間削減額:$7M(15.6%)
- サービスレベル:98%を維持
- 導入期間:18 か月
ネットワーク構成:
| DC | 所在地 | 種別 | 規模(SF) | 処理能力 | 固定費 | 年間取扱量 |
|---|---|---|---|---|---|---|
| DC1 | Memphis, TN | Regional | 500K | 120M cases | $8M | 95M |
| DC2 | LA, CA | Regional | 400K | 100M cases | $9M | 75M |
| DC3 | Newark, NJ | Regional | 450K | 110M cases | $8.5M | 85M |
| DC4 | Dallas, TX | Forward | 250K | 60M cases | $5M | 45M |
| DC5 | Atlanta, GA | Forward | 300K | 75M cases | $6M | 55M |
コスト比較:
| コスト区分 | 現在 | 提案 | 差額 | 変化率 |
|---|---|---|---|---|
| DC 固定費 | $18M | $15M | -$3M | -17% |
| DC 運営費 | $12M | $10M | -$2M | -17% |
| inbound 輸送 | $8M | $7M | -$1M | -13% |
| outbound 輸送 | $7M | $6M | -$1M | -14% |
| 合計 | $45M | $38M | -$7M | -15.6% |
サービスレベル分析:
| 指標 | 現在 | 提案 | 変化 |
|---|---|---|---|
| 顧客までの平均距離 | 420 mi | 385 mi | -35 mi |
| 2 日以内のサービス | 78% | 85% | +7 pts |
| 3 日以内のサービス | 94% | 96% | +2 pts |
| 充足率 | 96.5% | 98.0% | +1.5 pts |
確認事項
さらに詳しい情報が必要な場合:
- 製品カテゴリと販売チャネルは何ですか?
- 現在のネットワーク構成とコストはどうなっていますか?
- 対象となる地域はどこですか?
- SKU はいくつあり、出荷頻度の分布はどうなっていますか?
- 求められるサービスレベルはどの程度ですか?
- 販促や季節によるパターンはありますか?
- e コマースの割合と成長率はどの程度ですか?
- ネットワーク再設計の予算はいくらですか?
関連スキル
- network-design:一般的なネットワーク最適化手法
- facility-location-problem:数理モデル
- inventory-optimization:安全在庫と在庫配置
- promotional-planning:販促時の需要急増への対応
- dsd-route-optimization:店舗への直接配送
- warehouse-design:DC のレイアウトと自動化
- demand-forecasting:需要計画
- omnichannel-fulfillment:e コマースとの統合
- freight-optimization:輸送の最適化
コメント
Markdown 対応