import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
# --- 1. SETUP MAP & DATA ---
url = "https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip"
world = gpd.read_file(url)
north_america = world[world.CONTINENT == "North America"]
cities_data = {
'City': ['Los Angeles', 'New York', 'Dallas', 'Chicago'],
'Latitude': [34.05, 40.71, 32.77, 41.87],
'Longitude': [-118.24, -74.00, -96.79, -87.62],
'Mean_Demand': [150, 200, 80, 100],
'Volatility': [60, 40, 10, 20]
}
df = gpd.GeoDataFrame(
cities_data,
geometry=gpd.points_from_xy(cities_data['Longitude'], cities_data['Latitude']),
crs="EPSG:4326"
)
# --- 2. THE FIXED SIMULATION ENGINE ---
def run_lean_simulation(mean, std_dev):
SCENARIOS = 2000
WEEKS = 52
# AGGRESSIVE STRATEGY: Zero Safety Stock
# We order exactly the average demand.
# If demand is above average (50% of the time), we crash.
TARGET = mean * 1.2
# FIX: Warm Start (Start full, not empty)
current_inv = np.full(SCENARIOS, TARGET)
# Track total lost sales events
stockout_count = 0
for week in range(WEEKS):
demand = np.random.normal(mean, std_dev, SCENARIOS)
demand = np.maximum(demand, 0)
# LOGIC: If Demand > Inventory, we had a stockout
# (We count how many scenarios failed this week)
failures = demand > current_inv
stockout_count += np.sum(failures)
# Reorder (Instant replenishment for next week)
# We assume we refill to Target at start of next week
current_inv = np.full(SCENARIOS, TARGET)
# Calculate Probability of Stockout
total_opportunities = SCENARIOS * WEEKS
risk_score = (stockout_count / total_opportunities) * 100
return risk_score
# --- 3. RUN & VISUALIZE ---
print("Running 'Zero Safety Stock' Stress Test...")
df['Risk_Score'] = df.apply(
lambda row: run_lean_simulation(row['Mean_Demand'], row['Volatility']), axis=1
)
# PRINT THE RAW DATA (To verify it worked)
print(df[['City', 'Risk_Score']])
# PLOT
fig, ax = plt.subplots(figsize=(15, 10))
north_america.plot(ax=ax, color='#f0f0f0', edgecolor='white')
df.plot(ax=ax,
column='Risk_Score',
cmap='RdYlGn_r', # Red = High Risk
legend=True,
markersize=df['Mean_Demand'] * 5,
edgecolor='black',
alpha=0.9)
for x, y, label, risk in zip(df.geometry.x, df.geometry.y, df['City'], df['Risk_Score']):
ax.text(x + 1, y, f"{label}\nFail Rate: {risk:.1f}%", fontsize=11, fontweight='bold')
plt.title("Supply Chain Stress Test: Who Survives Zero Safety Stock?")
plt.axis('off')
plt.show()