skills/network-design/SKILL.md
When the user wants to design or optimize a supply chain network, determine facility locations, or configure distribution strategies. Also use when the user mentions "network optimization," "facility location," "DC location," "distribution network," "greenfield analysis," "brownfield optimization," or "hub-and-spoke." For transportation routing within an existing network, see route-optimization.
npx skillsauth add kishorkukreja/awesome-supply-chain network-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in supply chain network design and optimization. Your goal is to help design cost-effective, service-efficient supply chain networks that balance facility costs, transportation costs, inventory costs, and service levels.
Before designing the network, understand:
Business Context
Strategic Objectives
Data Availability
Constraints
1. Number of Echelons
2. Facility Types
3. Flow Strategies
Uncapacitated Facility Location (UFL)
Capacitated Facility Location (CFL)
P-Median Problem
P-Center Problem
Hub Location
Decision Variables:
Binary variables:
y_j = 1 if facility j is opened, 0 otherwise
Continuous variables:
x_ij = flow from facility j to customer i (units)
Objective Function:
Minimize:
Σ_j (f_j * y_j) # Fixed facility costs
+ Σ_i Σ_j (c_ij * x_ij) # Transportation costs
+ Σ_j (v_j * Σ_i x_ij) # Variable handling costs
+ Σ_j (h_j * Inventory_j) # Inventory carrying costs
Constraints:
1. Demand satisfaction:
Σ_j x_ij >= d_i ∀ customers i
2. Capacity constraints:
Σ_i x_ij <= Cap_j * y_j ∀ facilities j
3. Facility activation:
x_ij <= d_i * y_j ∀ i, j
4. Non-negativity:
x_ij >= 0, y_j ∈ {0,1}
from pulp import *
import pandas as pd
import numpy as np
def optimize_network(customers, facilities, demand, costs):
"""
Network design optimization using MIP
Parameters:
- customers: list of customer locations
- facilities: list of potential DC locations
- demand: dict {customer: demand_volume}
- costs: dict with 'fixed', 'transport', 'handling'
"""
# Create problem
prob = LpProblem("Network_Design", LpMinimize)
# Decision variables
# y[j] = 1 if facility j is opened
y = LpVariable.dicts("Facility",
facilities,
cat='Binary')
# x[i,j] = flow from facility j to customer i
x = LpVariable.dicts("Flow",
[(i,j) for i in customers for j in facilities],
lowBound=0,
cat='Continuous')
# Objective function
prob += (
# Fixed facility costs
lpSum([costs['fixed'][j] * y[j] for j in facilities]) +
# Transportation costs
lpSum([costs['transport'][i,j] * x[i,j]
for i in customers for j in facilities]) +
# Variable handling costs
lpSum([costs['handling'][j] * x[i,j]
for i in customers for j in facilities])
)
# Constraints
# 1. Each customer's demand must be satisfied
for i in customers:
prob += lpSum([x[i,j] for j in facilities]) >= demand[i], \
f"Demand_{i}"
# 2. Facility capacity constraints
for j in facilities:
prob += lpSum([x[i,j] for i in customers]) <= \
costs['capacity'][j] * y[j], \
f"Capacity_{j}"
# 3. Can only ship from open facilities
for i in customers:
for j in facilities:
prob += x[i,j] <= demand[i] * y[j], \
f"Open_{i}_{j}"
# Solve
prob.solve(PULP_CBC_CMD(msg=1))
# Extract results
results = {
'status': LpStatus[prob.status],
'total_cost': value(prob.objective),
'open_facilities': [j for j in facilities if y[j].varValue > 0.5],
'flows': {(i,j): x[i,j].varValue
for i in customers
for j in facilities
if x[i,j].varValue > 0.01}
}
return results
# Example usage
customers = ['Customer_A', 'Customer_B', 'Customer_C']
facilities = ['DC_1', 'DC_2', 'DC_3']
demand = {
'Customer_A': 1000,
'Customer_B': 1500,
'Customer_C': 800
}
costs = {
'fixed': {
'DC_1': 500000,
'DC_2': 600000,
'DC_3': 450000
},
'capacity': {
'DC_1': 3000,
'DC_2': 2500,
'DC_3': 2000
},
'transport': {
('Customer_A', 'DC_1'): 5.0,
('Customer_A', 'DC_2'): 8.0,
# ... (distance-based rates)
},
'handling': {
'DC_1': 2.0,
'DC_2': 2.5,
'DC_3': 1.8
}
}
result = optimize_network(customers, facilities, demand, costs)
print(f"Optimal cost: ${result['total_cost']:,.0f}")
print(f"Open facilities: {result['open_facilities']}")
Real Estate:
Labor:
Equipment:
Typical Ranges:
Truckload (TL):
Less-Than-Truckload (LTL):
Parcel:
Modeling Approach:
# Distance-based transportation cost
def transport_cost(distance_miles, weight_lbs, mode='TL'):
"""Calculate transportation cost"""
if mode == 'TL':
# Full truckload
if weight_lbs >= 40000: # Full truck
return distance_miles * 2.50
else:
# LTL or partial
return distance_miles * 3.00
elif mode == 'LTL':
# Less than truckload
cwt = weight_lbs / 100
# Distance brackets
if distance_miles < 500:
rate = 20
elif distance_miles < 1000:
rate = 35
else:
rate = 50
return cwt * rate
elif mode == 'Parcel':
# Simplified zone-based
zones = [150, 300, 600, 1000, 1400, 1800]
rates = [8, 12, 18, 25, 35, 45]
for i, zone_dist in enumerate(zones):
if distance_miles < zone_dist:
return rates[i]
return rates[-1]
Impact of Network on Inventory:
Square Root Law:
Total_Safety_Stock = Current_SS * sqrt(N_new / N_current)
Where N = number of stocking locations
def safety_stock_network(current_ss, current_dcs, new_dcs):
"""Estimate safety stock change from network redesign"""
factor = (new_dcs / current_dcs) ** 0.5
return current_ss * factor
# Example: consolidate 5 DCs into 2
current_inventory = 1000000 # units
new_inventory = safety_stock_network(
current_inventory,
current_dcs=5,
new_dcs=2
)
print(f"Inventory reduction: {current_inventory - new_inventory:,.0f} units")
# Output: ~367,544 unit reduction
Inventory Carrying Cost:
Delivery Time:
Fill Rate:
Trade-off Analysis:
def service_level_analysis(dc_locations, customer_locations):
"""Analyze service coverage from network"""
service_levels = {}
for dc in dc_locations:
# Calculate distances to all customers
distances = [
haversine_distance(dc, customer)
for customer in customer_locations
]
# Service zones (miles → days)
one_day = sum(1 for d in distances if d < 250)
two_day = sum(1 for d in distances if d < 600)
service_levels[dc] = {
'1-day_%': one_day / len(customer_locations) * 100,
'2-day_%': two_day / len(customer_locations) * 100
}
return service_levels
Customer Data:
Facility Data:
Product Data:
Transportation Data:
Strategic Options to Evaluate:
Centralization
Decentralization
Hybrid
Direct Ship
Base Scenarios:
Sensitivity Analysis:
Run Optimization Models:
# Multi-scenario optimization
scenarios = {
'Cost_Min': {'weight_cost': 1.0, 'weight_service': 0.0},
'Service_Max': {'weight_cost': 0.0, 'weight_service': 1.0},
'Balanced': {'weight_cost': 0.6, 'weight_service': 0.4}
}
results = {}
for name, weights in scenarios.items():
result = optimize_network_weighted(
customers, facilities, demand, costs,
alpha=weights['weight_cost'],
beta=weights['weight_service']
)
results[name] = result
# Compare scenarios
comparison_df = pd.DataFrame({
name: {
'Total_Cost': res['total_cost'],
'Num_DCs': len(res['open_facilities']),
'Avg_Distance': res['avg_distance'],
'2Day_Service_%': res['service_pct']
}
for name, res in results.items()
}).T
Pareto Frontier:
Final Recommendation:
Commercial:
Open Source:
# Complete network design example
import pulp
import pandas as pd
import numpy as np
from scipy.spatial.distance import cdist
class NetworkDesigner:
"""Supply chain network design optimizer"""
def __init__(self, customers_df, facilities_df):
"""
Initialize with customer and facility data
customers_df: columns = ['id', 'lat', 'lon', 'demand']
facilities_df: columns = ['id', 'lat', 'lon', 'fixed_cost', 'capacity']
"""
self.customers = customers_df
self.facilities = facilities_df
# Calculate distance matrix
self.distances = self._calculate_distances()
def _calculate_distances(self):
"""Calculate great-circle distances"""
cust_coords = self.customers[['lat', 'lon']].values
fac_coords = self.facilities[['lat', 'lon']].values
# Haversine distance in miles
distances = cdist(cust_coords, fac_coords, metric='euclidean')
distances *= 69 # Approx miles per degree
return distances
def optimize(self,
transport_rate=2.5,
handling_rate=1.0,
service_distance=None):
"""
Optimize network design
transport_rate: $/mile
handling_rate: $/unit
service_distance: max miles for service constraint
"""
prob = pulp.LpProblem("Network", pulp.LpMinimize)
# Variables
I = range(len(self.customers))
J = range(len(self.facilities))
y = pulp.LpVariable.dicts("Open", J, cat='Binary')
x = pulp.LpVariable.dicts("Flow",
[(i,j) for i in I for j in J],
lowBound=0)
# Objective
prob += (
# Fixed costs
pulp.lpSum([self.facilities.iloc[j]['fixed_cost'] * y[j]
for j in J]) +
# Transportation
pulp.lpSum([self.distances[i,j] * transport_rate * x[i,j]
for i in I for j in J]) +
# Handling
pulp.lpSum([handling_rate * x[i,j]
for i in I for j in J])
)
# Constraints
for i in I:
# Demand satisfaction
prob += pulp.lpSum([x[i,j] for j in J]) >= \
self.customers.iloc[i]['demand']
# Service distance constraint
if service_distance:
for j in J:
if self.distances[i,j] > service_distance:
prob += x[i,j] == 0
for j in J:
# Capacity
prob += pulp.lpSum([x[i,j] for i in I]) <= \
self.facilities.iloc[j]['capacity'] * y[j]
# Solve
prob.solve(pulp.PULP_CBC_CMD(msg=0))
# Results
return self._extract_results(prob, y, x, I, J)
def _extract_results(self, prob, y, x, I, J):
"""Extract and format results"""
open_dcs = [self.facilities.iloc[j]['id']
for j in J
if y[j].varValue > 0.5]
flows = []
for i in I:
for j in J:
if x[i,j].varValue > 0.01:
flows.append({
'customer': self.customers.iloc[i]['id'],
'facility': self.facilities.iloc[j]['id'],
'flow': x[i,j].varValue,
'distance': self.distances[i,j]
})
flows_df = pd.DataFrame(flows)
return {
'status': pulp.LpStatus[prob.status],
'total_cost': pulp.value(prob.objective),
'open_facilities': open_dcs,
'num_facilities': len(open_dcs),
'flows': flows_df,
'avg_distance': flows_df['distance'].mean(),
'max_distance': flows_df['distance'].max()
}
# Example usage
customers = pd.DataFrame({
'id': ['C1', 'C2', 'C3', 'C4'],
'lat': [34.05, 41.88, 39.74, 29.76],
'lon': [-118.24, -87.63, -104.99, -95.37],
'demand': [1000, 1500, 800, 1200]
})
facilities = pd.DataFrame({
'id': ['DC_West', 'DC_Central', 'DC_East'],
'lat': [36.17, 39.10, 40.71],
'lon': [-115.14, -94.58, -74.01],
'fixed_cost': [500000, 450000, 600000],
'capacity': [4000, 3500, 3000]
})
designer = NetworkDesigner(customers, facilities)
result = designer.optimize(transport_rate=2.5, handling_rate=1.0)
print(f"Total Cost: ${result['total_cost']:,.0f}")
print(f"Open DCs: {result['open_facilities']}")
print(f"Avg Distance: {result['avg_distance']:.1f} miles")
Consider:
# Multi-period formulation (simplified)
T = range(planning_horizon) # periods
# Variables
y = {} # y[j,t] = facility j opened in period t
z = {} # z[j,t] = facility j operating in period t
for t in T:
for j in J:
y[j,t] = pulp.LpVariable(f"Open_{j}_{t}", cat='Binary')
z[j,t] = pulp.LpVariable(f"Operating_{j}_{t}", cat='Binary')
# Constraint: once opened, stays open
for t in range(1, len(T)):
for j in J:
prob += z[j,t] >= z[j,t-1]
Additional Considerations:
Landed Cost Calculation:
def landed_cost(product_cost, transport, duty_rate, tax_rate):
"""Calculate total landed cost"""
customs_value = product_cost + transport
duty = customs_value * duty_rate
taxable_value = customs_value + duty
tax = taxable_value * tax_rate
total = product_cost + transport + duty + tax
return total
Uncertainty in:
Two-Stage Stochastic Programming:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Problem:
Solutions:
Executive Summary:
Network Configuration:
| Facility | Location | Type | Capacity | Fixed Cost | Customers Served | Annual Throughput | |----------|----------|------|----------|------------|------------------|-------------------| | DC_1 | Memphis, TN | Full-line DC | 500K units | $5M | 1,250 | 450K | | DC_2 | Los Angeles, CA | Regional DC | 300K units | $4M | 800 | 280K |
Cost Breakdown:
| Cost Category | Current | Proposed | Delta | % Change | |---------------|---------|----------|-------|----------| | Fixed Facility | $12M | $9M | -$3M | -25% | | Transportation | $15M | $14M | -$1M | -7% | | Inventory Carrying | $8M | $6M | -$2M | -25% | | Total | $35M | $29M | -$6M | -17% |
Service Levels:
| Metric | Current | Proposed | Improvement | |--------|---------|----------|-------------| | Avg. Distance | 650 mi | 420 mi | -35% | | 2-Day Service % | 75% | 92% | +17 pts | | On-Time Delivery | 89% | 95% | +6 pts |
Implementation Plan:
If you need more context:
documentation
When the user wants to optimize yard operations, manage trailer parking, or improve dock door utilization. Also use when the user mentions "yard management," "trailer tracking," "yard jockey," "drop trailer program," "trailer pool," "dock scheduling," or "gate management." For cross-dock operations, see cross-docking. For warehouse design, see warehouse-design.
tools
When the user wants to optimize workforce scheduling, create shift plans, or balance labor demand. Also use when the user mentions "staff scheduling," "labor planning," "shift optimization," "crew scheduling," "roster optimization," or "employee scheduling." For task assignment, see task-assignment-problem. For wave planning labor, see wave-planning-optimization.
testing
When the user wants to optimize pick wave planning, schedule warehouse operations, or improve order fulfillment efficiency. Also use when the user mentions "wave management," "batch picking," "pick wave scheduling," "order release optimization," "wave design," or "pick wave strategy." For order batching, see order-batching-optimization. For workforce scheduling, see workforce-scheduling.
testing
When the user wants to optimize warehouse slot assignments, improve pick efficiency, or design warehouse layouts. Also use when the user mentions "slotting optimization," "slot assignment," "ABC slotting," "pick path optimization," "storage location assignment," "warehouse layout optimization," or "forward pick locations." For picker routing, see picker-routing-optimization. For warehouse design, see warehouse-design.