Spaces:
Sleeping
Sleeping
#Stable version for Yazaki India Ltd | |
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
import plotly.express as px | |
import plotly.graph_objects as go | |
from datetime import datetime, timedelta | |
import random | |
# Page configuration | |
st.set_page_config( | |
page_title="Yazaki India Ltd - Complete Supply Chain Hub", | |
page_icon="🌐", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Custom CSS (same as before) | |
st.markdown(""" | |
<style> | |
.tab-header { | |
background: linear-gradient(90deg, #059669, #10b981); | |
padding: 0.8rem; | |
border-radius: 8px; | |
color: white; | |
margin-bottom: 1rem; | |
} | |
.alert-card { | |
background: #fff5f5; | |
padding: 1rem; | |
border-radius: 8px; | |
border-left: 6px solid #e53e3e; | |
margin: 0.5rem 0; | |
} | |
.ecosystem-alert { | |
background: #fef2f2; | |
padding: 1rem; | |
border-radius: 8px; | |
border-left: 6px solid #dc2626; | |
margin: 0.5rem 0; | |
} | |
.root-cause { | |
background: #fef7e7; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 3px solid #f6ad55; | |
} | |
.mitigation { | |
background: #e6fffa; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 3px solid #4fd1c7; | |
} | |
.best-option { | |
background: #f0fff4; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 4px solid #48bb78; | |
border: 2px solid #48bb78; | |
} | |
.tier-impact { | |
background: #fff7ed; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 4px solid #f97316; | |
} | |
.mitigation-executed { | |
background: #ecfdf5; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 4px solid #10b981; | |
border: 2px solid #10b981; | |
} | |
.mitigation-recommended { | |
background: #eff6ff; | |
padding: 0.8rem; | |
border-radius: 6px; | |
margin: 0.3rem 0; | |
border-left: 4px solid #3b82f6; | |
} | |
.normal-status { | |
background: #f0fff4; | |
padding: 0.6rem; | |
border-radius: 6px; | |
border-left: 4px solid #48bb78; | |
margin: 0.2rem 0; | |
} | |
.external-signal { | |
background: #f3e5f5; | |
padding: 0.6rem; | |
border-radius: 6px; | |
border-left: 4px solid #9c27b0; | |
margin: 0.2rem 0; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Initialize session state | |
if 'executed_mitigations' not in st.session_state: | |
st.session_state.executed_mitigations = [] | |
if 'external_signals' not in st.session_state: | |
st.session_state.external_signals = [] | |
# UPDATED: Generate 8-week forward-looking demand data | |
def generate_8week_demand_data(): | |
today = datetime(2025, 8, 4) | |
dates = [today + timedelta(days=x) for x in range(56)] # 8 weeks = 56 days | |
materials = [ | |
'YAZ001-Wiring Harness', | |
'YAZ002-Connectors', | |
'YAZ003-Terminals', | |
'YAZ004-Sensors', | |
'YAZ005-Cable Assemblies' | |
] | |
all_data = [] | |
for material in materials: | |
np.random.seed(hash(material) % 1000) | |
# Generate base demand patterns | |
base_demand = np.random.normal(150, 15, 56) | |
# First 14 days: FIRM DEMAND | |
firm_demand = np.clip(base_demand[:14], 100, 200).astype(int) | |
# Days 15-56: Customer shared demand (tentative) | |
customer_shared = np.clip(base_demand[14:] * (1 + 0.05 * np.sin(np.linspace(0, 3.14, 42))), 80, 220).astype(int) | |
# Days 15-56: AI-corrected demand (with external signals) | |
external_factors = np.zeros(42) | |
# Weather impact (weeks 3-4) | |
external_factors[0:14] += np.random.normal(0, 5, 14) | |
# EV policy impact (weeks 5-8) | |
if 'YAZ' in material: | |
external_factors[14:] += 10 | |
# Festive season boost (weeks 6-7) | |
external_factors[28:42] += 8 | |
corrected_demand = np.clip(customer_shared + external_factors, 60, 250).astype(int) | |
# Generate supply plan for 56 days | |
supply_capacity = np.random.normal(155, 12, 56) | |
supply_plan = np.clip(supply_capacity, 120, 220).astype(int) | |
# Apply disruptions to supply (weather impact on days 15-18) | |
supply_actual = supply_plan.copy() | |
supply_actual[15:19] = (supply_actual[15:19] * 0.8).astype(int) | |
for i, date in enumerate(dates): | |
# Determine which demand to use | |
if i < 14: | |
demand_used = firm_demand[i] | |
firm_val = firm_demand[i] | |
customer_val = None | |
corrected_val = None | |
demand_type = "Firm" | |
else: | |
demand_used = corrected_demand[i-14] | |
firm_val = None | |
customer_val = customer_shared[i-14] | |
corrected_val = corrected_demand[i-14] | |
demand_type = "AI-Corrected" | |
# Calculate shortfall | |
shortfall = max(0, demand_used - supply_actual[i]) | |
all_data.append({ | |
'Date': date, | |
'Week': f"Week {(i//7)+1}", | |
'Day': i + 1, | |
'Material': material, | |
'Firm_Demand': firm_val, | |
'Customer_Demand': customer_val, | |
'Corrected_Demand': corrected_val, | |
'Demand_Used': demand_used, | |
'Supply_Plan': supply_plan[i], | |
'Supply_Projected': supply_actual[i], | |
'Shortfall': shortfall, | |
'Demand_Type': demand_type, | |
'Gap': supply_actual[i] - demand_used | |
}) | |
return pd.DataFrame(all_data) | |
# UPDATED: Tier-2 suppliers for Yazaki India | |
def get_tier2_suppliers(): | |
return { | |
'Electro Components Pvt Ltd': { | |
'location': 'Chennai', | |
'materials': ['YAZ001-Wiring Harness', 'YAZ002-Connectors'], | |
'capacity': 210, | |
'reliability': 93, | |
'lead_time': 3, | |
'risk_factors': ['Port delays', 'Power outages', 'Labor strikes'] | |
}, | |
'Connectix Solutions': { | |
'location': 'Ahmedabad', | |
'materials': ['YAZ003-Terminals', 'YAZ004-Sensors'], | |
'capacity': 190, | |
'reliability': 90, | |
'lead_time': 2, | |
'risk_factors': ['Raw material shortage', 'Transportation issues', 'Equipment failure'] | |
}, | |
'WireCraft Industries': { | |
'location': 'Pune', | |
'materials': ['YAZ005-Cable Assemblies', 'YAZ001-Wiring Harness'], | |
'capacity': 230, | |
'reliability': 87, | |
'lead_time': 1, | |
'risk_factors': ['Quality checks', 'Capacity limits', 'Supplier disputes'] | |
} | |
} | |
# UPDATED: Ecosystem data with Yazaki-specific naming | |
def generate_ecosystem_data(): | |
today = datetime(2025, 8, 4) | |
dates = [today + timedelta(days=x) for x in range(14)] | |
suppliers = get_tier2_suppliers() | |
all_data = [] | |
for supplier_name, supplier_info in suppliers.items(): | |
for material in supplier_info['materials']: | |
np.random.seed(hash(supplier_name + material) % 1000) | |
base_capacity = supplier_info['capacity'] | |
normal_supply = np.full(14, base_capacity, dtype=int) | |
disrupted_supply = normal_supply.copy() | |
if supplier_name == 'Electro Components Pvt Ltd': | |
disrupted_supply[3:7] = (disrupted_supply[3:7] * 0.3).astype(int) | |
disruption_cause = "Port delays in Chennai affecting imports" | |
disruption_days = list(range(3, 7)) | |
elif supplier_name == 'Connectix Solutions': | |
disrupted_supply[5:8] = (disrupted_supply[5:8] * 0.5).astype(int) | |
disruption_cause = "Critical equipment failure at Ahmedabad facility" | |
disruption_days = list(range(5, 8)) | |
elif supplier_name == 'WireCraft Industries': | |
disrupted_supply[8:11] = (disrupted_supply[8:11] * 0.2).astype(int) | |
disruption_cause = "Labor strike at Pune facility" | |
disruption_days = list(range(8, 11)) | |
else: | |
disruption_cause = "No disruption" | |
disruption_days = [] | |
lead_time = supplier_info['lead_time'] | |
yazaki_supply = np.full(14, base_capacity, dtype=int) | |
for disruption_day in disruption_days: | |
arrival_day = disruption_day + lead_time | |
if arrival_day < 14: | |
reduction = normal_supply[disruption_day] - disrupted_supply[disruption_day] | |
yazaki_supply[arrival_day] = max(yazaki_supply[arrival_day] - reduction, 0) | |
for i, date in enumerate(dates): | |
all_data.append({ | |
'Date': date, | |
'Supplier': supplier_name, | |
'Material': material, | |
'Tier2_Normal_Supply': int(normal_supply[i]), | |
'Tier2_Disrupted_Supply': int(disrupted_supply[i]), | |
'Tier2_Impact': int(normal_supply[i] - disrupted_supply[i]), | |
'Yazaki_Normal_Supply': int(normal_supply[i]), | |
'Yazaki_Impacted_Supply': int(yazaki_supply[i]), | |
'Yazaki_Impact': int(normal_supply[i] - yazaki_supply[i]), | |
'Disruption_Cause': disruption_cause if i in disruption_days else "Normal Operations", | |
'Lead_Time_Days': lead_time, | |
'Is_Disrupted': i in disruption_days, | |
'Is_Yazaki_Impacted': yazaki_supply[i] < normal_supply[i] | |
}) | |
return pd.DataFrame(all_data) | |
# External signals (unchanged) | |
def get_external_signals(): | |
return [ | |
{'Source': 'Weather API', 'Signal': 'Heavy rains forecasted in Chennai for next 3 days', 'Impact': 'Supply Risk', 'Confidence': 95}, | |
{'Source': 'Market Intelligence', 'Signal': 'EV sales up 25% this quarter', 'Impact': 'Demand Increase', 'Confidence': 88}, | |
{'Source': 'News Analytics', 'Signal': 'Upcoming festive season - historically 15% demand spike', 'Impact': 'Demand Surge', 'Confidence': 92}, | |
{'Source': 'Supplier Network', 'Signal': 'Tier-2 supplier capacity increased by 20%', 'Impact': 'Supply Boost', 'Confidence': 98}, | |
{'Source': 'Social Media', 'Signal': 'Positive sentiment around new Maruti EV model', 'Impact': 'Demand Growth', 'Confidence': 75}, | |
{'Source': 'Government Portal', 'Signal': 'New EV subsidy policy effective next week', 'Impact': 'Market Expansion', 'Confidence': 100} | |
] | |
# UPDATED: Generate alerts for 8-week data | |
def generate_detailed_alerts(df): | |
alerts = [] | |
for material in df['Material'].unique(): | |
material_data = df[df['Material'] == material] | |
shortage_days = material_data[material_data['Shortfall'] > 5] | |
if not shortage_days.empty: | |
for _, row in shortage_days.iterrows(): | |
root_causes = [] | |
if row['Day'] > 14: | |
if row['Corrected_Demand'] and row['Customer_Demand']: | |
diff = row['Corrected_Demand'] - row['Customer_Demand'] | |
if diff > 10: | |
root_causes.append(f"AI detected {diff} units additional demand from external signals") | |
if row['Day'] >= 15 and row['Day'] <= 18: | |
root_causes.append("Chennai plant weather disruption reducing supply") | |
else: | |
root_causes.append("Firm demand exceeding supply capacity") | |
if not root_causes: | |
root_causes.append("Base demand exceeding current supply capacity") | |
mitigation_options = [ | |
{"option": "Activate Pune backup production", "impact": "+30 units/day", "cost": "High", "timeline": "24 hours"}, | |
{"option": "Expedite Tier-2 supplier shipments", "impact": "+15 units/day", "cost": "Medium", "timeline": "12 hours"}, | |
{"option": "Emergency air freight from backup suppliers", "impact": "+40 units/day", "cost": "Very High", "timeline": "6 hours"}, | |
{"option": "Reallocate inventory from other plants", "impact": "+20 units/day", "cost": "Low", "timeline": "18 hours"} | |
] | |
if row['Shortfall'] > 30: | |
best_option = mitigation_options[2] | |
elif row['Shortfall'] > 15: | |
best_option = mitigation_options[0] | |
else: | |
best_option = mitigation_options[1] | |
alerts.append({ | |
'material': material, | |
'date': row['Date'].strftime('%Y-%m-%d'), | |
'week': row['Week'], | |
'shortage': int(row['Shortfall']), | |
'demand_type': row['Demand_Type'], | |
'severity': 'Critical' if row['Shortfall'] > 30 else 'High' if row['Shortfall'] > 15 else 'Medium', | |
'root_causes': root_causes, | |
'mitigation_options': mitigation_options, | |
'best_option': best_option | |
}) | |
return alerts | |
# Keep mitigation strategies unchanged | |
def generate_mitigation_strategies(supplier, material, impact_amount, impact_days): | |
base_strategies = [ | |
{ | |
'strategy': 'Activate Alternate Supplier', | |
'description': f'Engage backup supplier for {material}', | |
'timeline': '24-48 hours', | |
'cost': 'High (+15% unit cost)', | |
'effectiveness': '90%', | |
'capacity': f'+{impact_amount * 0.9:.0f} units/day', | |
}, | |
{ | |
'strategy': 'Emergency Air Freight', | |
'description': f'Air freight {material} from other regions', | |
'timeline': '6-12 hours', | |
'cost': 'Very High (+40% logistics cost)', | |
'effectiveness': '75%', | |
'capacity': f'+{impact_amount * 0.75:.0f} units/day', | |
}, | |
{ | |
'strategy': 'Inventory Reallocation', | |
'description': f'Reallocate {material} from other plants', | |
'timeline': '12-24 hours', | |
'cost': 'Medium (+5% handling cost)', | |
'effectiveness': '60%', | |
'capacity': f'+{impact_amount * 0.6:.0f} units/day', | |
} | |
] | |
if impact_amount > 100: | |
recommended = [0, 1] | |
elif impact_amount > 50: | |
recommended = [0, 2] | |
else: | |
recommended = [2] | |
return base_strategies, recommended | |
# Load data | |
df_demand = generate_8week_demand_data() | |
df_ecosystem = generate_ecosystem_data() | |
external_signals = get_external_signals() | |
suppliers = get_tier2_suppliers() | |
# Simple title (header removed as requested) | |
st.title("Supply Chain Command Center") | |
# Tab Navigation (same as before) | |
st.sidebar.title("🎯 Dashboard Navigation") | |
dashboard_tab = st.sidebar.radio( | |
"Select Dashboard:", | |
["📊 Demand & Supply Forecast", "🌐 Ecosystem Supplier Impact", "🛡️ Buffer Optimizer"], | |
index=0 | |
) | |
# UPDATED TAB 1: 8-WEEK DEMAND & SUPPLY FORECAST | |
if dashboard_tab == "📊 Demand & Supply Forecast": | |
st.markdown(""" | |
<div class="tab-header"> | |
<h2>📊 8-Week Demand & Supply Forecast Dashboard</h2> | |
<p>8-Week Planning Horizon | Firm Demand (Days 1-14) | AI-Corrected Demand (Days 15-56)</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# Material selection | |
selected_materials_demand = st.sidebar.multiselect( | |
"Focus Materials:", | |
df_demand['Material'].unique(), | |
default=df_demand['Material'].unique()[:3] | |
) | |
# Week filter | |
week_filter = st.sidebar.selectbox( | |
"Focus on Weeks:", | |
["All 8 Weeks", "Weeks 1-2 (Firm)", "Weeks 3-4", "Weeks 5-6", "Weeks 7-8"], | |
index=0 | |
) | |
# Filter data | |
filtered_df_demand = df_demand[df_demand['Material'].isin(selected_materials_demand)] | |
if week_filter != "All 8 Weeks": | |
if week_filter == "Weeks 1-2 (Firm)": | |
filtered_df_demand = filtered_df_demand[filtered_df_demand['Day'] <= 14] | |
elif week_filter == "Weeks 3-4": | |
filtered_df_demand = filtered_df_demand[(filtered_df_demand['Day'] > 14) & (filtered_df_demand['Day'] <= 28)] | |
elif week_filter == "Weeks 5-6": | |
filtered_df_demand = filtered_df_demand[(filtered_df_demand['Day'] > 28) & (filtered_df_demand['Day'] <= 42)] | |
else: # Weeks 7-8 | |
filtered_df_demand = filtered_df_demand[filtered_df_demand['Day'] > 42] | |
# Generate and display alerts | |
st.subheader("🚨 8-Week Supply Chain Alerts") | |
alerts = generate_detailed_alerts(filtered_df_demand) | |
if alerts: | |
for i, alert in enumerate(alerts[:3]): | |
st.markdown(f""" | |
<div class="alert-card"> | |
<h4>⚠️ {alert['material']} - {alert['severity']} Shortage Alert</h4> | |
<p><b>Date:</b> {alert['date']} ({alert['week']}) | <b>Shortage:</b> {alert['shortage']} units | <b>Type:</b> {alert['demand_type']}</p> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown("**🔍 Root Cause Analysis:**") | |
for cause in alert['root_causes']: | |
st.markdown(f""" | |
<div class="root-cause"> | |
🎯 {cause} | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown("**⚡ Mitigation Options:**") | |
for option in alert['mitigation_options']: | |
is_best = option == alert['best_option'] | |
option_class = "best-option" if is_best else "mitigation" | |
best_indicator = "🏆 **RECOMMENDED** " if is_best else "" | |
st.markdown(f""" | |
<div class="{option_class}"> | |
{best_indicator}<b>{option['option']}</b><br> | |
📈 Impact: {option['impact']} | 💰 Cost: {option['cost']} | ⏱️ Timeline: {option['timeline']} | |
</div> | |
""", unsafe_allow_html=True) | |
col1, col2, col3 = st.columns([2, 1, 1]) | |
with col1: | |
if st.button(f"✅ Implement Solution", key=f"demand_implement_{i}"): | |
st.success(f"Implementing: {alert['best_option']['option']}") | |
st.markdown("---") | |
else: | |
st.markdown(""" | |
<div class="normal-status"> | |
✅ <b>All Good!</b> No critical supply shortages detected in the 8-week horizon. | |
</div> | |
""", unsafe_allow_html=True) | |
# UPDATED: 8-Week Detailed Planning Table | |
st.subheader("📋 8-Week Demand-Supply Planning Table") | |
# Prepare display table | |
display_df = filtered_df_demand.copy() | |
display_df['Date_Display'] = display_df['Date'].dt.strftime('%m-%d') | |
# Create styled table | |
table_cols = ['Date_Display', 'Week', 'Material', 'Firm_Demand', 'Customer_Demand', | |
'Corrected_Demand', 'Supply_Projected', 'Shortfall'] | |
table_data = display_df[table_cols].copy() | |
table_data.columns = ['Date', 'Week', 'Material', 'Firm Demand', 'Customer Demand', | |
'Corrected Demand', 'Supply Plan', 'Shortfall'] | |
# Color coding function | |
def highlight_shortfall(val): | |
if pd.isna(val): | |
return '' | |
return 'background-color: #ffcccc' if val > 0 else '' | |
def highlight_firm_period(row): | |
if pd.notna(row['Firm Demand']): | |
return ['background-color: #e6f3ff'] * len(row) | |
return [''] * len(row) | |
# Apply styling | |
styled_table = table_data.style.applymap(highlight_shortfall, subset=['Shortfall']) | |
styled_table = styled_table.apply(highlight_firm_period, axis=1) | |
st.dataframe(styled_table, use_container_width=True, height=500) | |
# Weekly summary | |
st.subheader("📊 Weekly Summary") | |
weekly_summary = filtered_df_demand.groupby(['Week', 'Material']).agg({ | |
'Demand_Used': 'sum', | |
'Supply_Projected': 'sum', | |
'Shortfall': 'sum' | |
}).reset_index() | |
weekly_summary['Balance'] = weekly_summary['Supply_Projected'] - weekly_summary['Demand_Used'] | |
st.dataframe(weekly_summary, use_container_width=True) | |
# Enhanced visualization | |
st.subheader("📈 8-Week Demand vs Supply Outlook") | |
for material in selected_materials_demand: | |
material_data = filtered_df_demand[filtered_df_demand['Material'] == material] | |
st.markdown(f"**{material}**") | |
fig = go.Figure() | |
# Add demand used line | |
fig.add_trace(go.Scatter( | |
x=material_data['Date'], | |
y=material_data['Demand_Used'], | |
mode='lines+markers', | |
name='Demand Used', | |
line=dict(color='blue', width=3), | |
marker=dict(size=6) | |
)) | |
# Add supply line | |
fig.add_trace(go.Scatter( | |
x=material_data['Date'], | |
y=material_data['Supply_Projected'], | |
mode='lines+markers', | |
name='Supply Projected', | |
line=dict(color='green', width=3), | |
marker=dict(size=6) | |
)) | |
# Highlight shortfall areas | |
shortage_data = material_data[material_data['Shortfall'] > 0] | |
if not shortage_data.empty: | |
fig.add_trace(go.Scatter( | |
x=shortage_data['Date'], | |
y=shortage_data['Supply_Projected'], | |
mode='markers', | |
name='Shortage Days', | |
marker=dict(color='red', size=10, symbol='x'), | |
)) | |
# Mark firm demand period | |
firm_data = material_data[material_data['Day'] <= 14] | |
if not firm_data.empty: | |
fig.add_vrect( | |
x0=firm_data['Date'].min(), | |
x1=firm_data['Date'].max(), | |
fillcolor="lightblue", | |
opacity=0.2, | |
line_width=0, | |
annotation_text="Firm Demand Period", | |
annotation_position="top left" | |
) | |
fig.update_layout( | |
title=f'{material} - 8-Week Supply vs Demand Forecast', | |
xaxis_title='Date', | |
yaxis_title='Units', | |
height=400, | |
showlegend=True, | |
hovermode='x unified' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# External demand sensing (same as before) | |
st.subheader("📡 Real-time External Demand Sensing") | |
col1, col2 = st.columns(2) | |
with col1: | |
st.write("**Active External Signals:**") | |
for signal in external_signals: | |
confidence_color = "🟢" if signal['Confidence'] > 90 else "🟡" if signal['Confidence'] > 80 else "🟠" | |
st.markdown(f""" | |
<div class="external-signal"> | |
<b>{confidence_color} {signal['Source']}</b><br> | |
{signal['Signal']}<br> | |
<small>Impact: {signal['Impact']} | Confidence: {signal['Confidence']}%</small> | |
</div> | |
""", unsafe_allow_html=True) | |
with col2: | |
st.write("**8-Week Scenario Planning:**") | |
scenario = st.selectbox("Select Scenario to Test:", | |
["Base Case", "Extended Monsoon", "Sustained EV Boost", "Supply Chain Strike"]) | |
if st.button("🎮 Run 8-Week Scenario", key="demand_scenario"): | |
if scenario == "Extended Monsoon": | |
st.error("Scenario: 30% supply reduction for 3 weeks. Activating multi-tier contingency plans...") | |
elif scenario == "Sustained EV Boost": | |
st.warning("Scenario: 25% demand increase for 6 weeks. Scaling ecosystem capacity...") | |
elif scenario == "Supply Chain Strike": | |
st.info("Scenario: Multi-supplier disruption. Implementing emergency protocols...") | |
# Keep TAB 2 and TAB 3 unchanged from previous version, but replace Rane with Yazaki in variables and text | |
elif dashboard_tab == "🌐 Ecosystem Supplier Impact": | |
st.markdown(""" | |
<div class="tab-header"> | |
<h2>🌐 Ecosystem Supplier Impact Dashboard</h2> | |
<p>Tier 2 Supplier Disruption Analysis | Cascading Impact Modeling | Automated Mitigation Response</p> | |
</div> | |
""", unsafe_allow_html=True) | |
selected_suppliers = st.sidebar.multiselect( | |
"Monitor Suppliers:", | |
list(suppliers.keys()), | |
default=list(suppliers.keys()) | |
) | |
st.subheader("🚨 Live Ecosystem Supply Chain Alerts") | |
ecosystem_alerts = [] | |
for supplier in selected_suppliers: | |
supplier_data = df_ecosystem[df_ecosystem['Supplier'] == supplier] | |
disrupted_data = supplier_data[supplier_data['Is_Disrupted'] == True] | |
if not disrupted_data.empty: | |
for material in disrupted_data['Material'].unique(): | |
material_disruptions = disrupted_data[disrupted_data['Material'] == material] | |
total_impact = material_disruptions['Tier2_Impact'].sum() | |
impact_days = len(material_disruptions) | |
first_impact_date = material_disruptions['Date'].min() | |
yazaki_impacted = supplier_data[ | |
(supplier_data['Material'] == material) & | |
(supplier_data['Is_Yazaki_Impacted'] == True) | |
] | |
if not yazaki_impacted.empty: | |
yazaki_impact_start = yazaki_impacted['Date'].min() | |
yazaki_impact_days = len(yazaki_impacted) | |
yazaki_total_impact = yazaki_impacted['Yazaki_Impact'].sum() | |
ecosystem_alerts.append({ | |
'supplier': supplier, | |
'material': material, | |
'disruption_cause': material_disruptions.iloc[0]['Disruption_Cause'], | |
'tier2_impact_start': first_impact_date, | |
'tier2_impact_days': impact_days, | |
'tier2_total_impact': total_impact, | |
'yazaki_impact_start': yazaki_impact_start, | |
'yazaki_impact_days': yazaki_impact_days, | |
'yazaki_total_impact': yazaki_total_impact, | |
'lead_time': material_disruptions.iloc[0]['Lead_Time_Days'] | |
}) | |
if ecosystem_alerts: | |
for alert in ecosystem_alerts: | |
st.markdown(f""" | |
<div class="ecosystem-alert"> | |
<h4>⚠️ Tier 2 Supplier Disruption Alert</h4> | |
<p><b>Supplier:</b> {alert['supplier']} | <b>Material:</b> {alert['material']}</p> | |
<p><b>Root Cause:</b> {alert['disruption_cause']}</p> | |
</div> | |
""", unsafe_allow_html=True) | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown("**🏭 Tier 2 Supplier Impact:**") | |
st.markdown(f""" | |
<div class="tier-impact"> | |
📅 <b>Impact Period:</b> {alert['tier2_impact_start'].strftime('%Y-%m-%d')} ({alert['tier2_impact_days']} days)<br> | |
📉 <b>Total Supply Lost:</b> {alert['tier2_total_impact']} units<br> | |
🎯 <b>Daily Impact:</b> {alert['tier2_total_impact'] // alert['tier2_impact_days']} units/day | |
</div> | |
""", unsafe_allow_html=True) | |
with col2: | |
st.markdown("**⚙️ Yazaki India Ltd Impact (with Lead Time):**") | |
st.markdown(f""" | |
<div class="tier-impact"> | |
📅 <b>Impact Period:</b> {alert['yazaki_impact_start'].strftime('%Y-%m-%d')} ({alert['yazaki_impact_days']} days)<br> | |
📉 <b>Total Supply Lost:</b> {alert['yazaki_total_impact']} units<br> | |
⏱️ <b>Lead Time Delay:</b> {alert['lead_time']} days | |
</div> | |
""", unsafe_allow_html=True) | |
strategies, recommended_indices = generate_mitigation_strategies( | |
alert['supplier'], | |
alert['material'], | |
alert['yazaki_total_impact'] // alert['yazaki_impact_days'], | |
alert['yazaki_impact_days'] | |
) | |
st.markdown("**🤖 Agentic AI Mitigation Strategies:**") | |
for i, strategy in enumerate(strategies): | |
is_recommended = i in recommended_indices | |
is_executed = f"eco_{alert['supplier']}_{alert['material']}_{i}" in st.session_state.executed_mitigations | |
if is_executed: | |
card_class = "mitigation-executed" | |
status_prefix = "✅ **EXECUTED** " | |
elif is_recommended: | |
card_class = "mitigation-recommended" | |
status_prefix = "🏆 **AI RECOMMENDED** " | |
else: | |
card_class = "mitigation-recommended" | |
status_prefix = "" | |
st.markdown(f""" | |
<div class="{card_class}"> | |
{status_prefix}<b>{strategy['strategy']}</b><br> | |
📋 {strategy['description']}<br> | |
⏱️ <b>Timeline:</b> {strategy['timeline']} | 💰 <b>Cost:</b> {strategy['cost']}<br> | |
📈 <b>Effectiveness:</b> {strategy['effectiveness']} | 🚀 <b>Capacity:</b> {strategy['capacity']} | |
</div> | |
""", unsafe_allow_html=True) | |
strategy_key = f"eco_{alert['supplier']}_{alert['material']}_{i}" | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
if not is_executed: | |
if st.button(f"🚀 Execute Strategy", key=f"execute_{strategy_key}"): | |
st.session_state.executed_mitigations.append(strategy_key) | |
st.success(f"Executing: {strategy['strategy']}") | |
st.rerun() | |
else: | |
st.success("Strategy Active") | |
with col2: | |
if is_recommended: | |
st.button("🏆 Recommended", key=f"rec_{strategy_key}", disabled=True) | |
st.markdown("---") | |
else: | |
st.markdown(""" | |
<div class="normal-status"> | |
✅ <b>Ecosystem Healthy!</b> No supplier disruptions detected in the current timeframe. | |
</div> | |
""", unsafe_allow_html=True) | |
st.subheader("📊 Ecosystem Supply Chain Flow Visualization") | |
fig = go.Figure() | |
for supplier in selected_suppliers: | |
supplier_data = df_ecosystem[df_ecosystem['Supplier'] == supplier] | |
sample_material = supplier_data['Material'].iloc[0] | |
material_data = supplier_data[supplier_data['Material'] == sample_material] | |
fig.add_trace(go.Scatter( | |
x=material_data['Date'], | |
y=material_data['Tier2_Disrupted_Supply'], | |
mode='lines+markers', | |
name=f'{supplier} (Tier 2)', | |
line=dict(width=2, dash='dash'), | |
marker=dict(size=6) | |
)) | |
fig.add_trace(go.Scatter( | |
x=material_data['Date'], | |
y=material_data['Yazaki_Impacted_Supply'], | |
mode='lines+markers', | |
name=f'Yazaki Impact from {supplier}', | |
line=dict(width=3), | |
marker=dict(size=8) | |
)) | |
fig.update_layout( | |
title='Tier 2 Supplier Disruptions → Yazaki India Ltd Supply Impact', | |
xaxis_title='Date', | |
yaxis_title='Supply Units', | |
height=500, | |
showlegend=True, | |
hovermode='x unified' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# TAB 3: BUFFER OPTIMIZER (same as before) | |
elif dashboard_tab == "🛡️ Buffer Optimizer": | |
st.markdown(""" | |
<div class="tab-header"> | |
<h2>🛡️ Multi-Echelon Buffer Optimizer</h2> | |
<p>AI-driven safety-stock recommendations across the full network</p> | |
</div> | |
""", unsafe_allow_html=True) | |
service_level = st.slider("Target Service Level (%)", 90, 99, 95) | |
review_period = st.number_input("Inventory Review Period (days)", min_value=1, max_value=14, value=1) | |
z_factor = {90: 1.28, 92: 1.41, 95: 1.64, 97: 1.88, 98: 2.05, 99: 2.33} | |
Z = z_factor.get(service_level, 1.64) | |
# Use 8-week demand data for buffer calculation | |
demand_stats = (df_demand | |
.groupby("Material") | |
.agg(DailyMean=("Demand_Used", "mean"), | |
Sigma=("Demand_Used", "std")) | |
.reset_index()) | |
lead_times = (df_ecosystem | |
.groupby("Material") | |
.agg(LeadTime=("Lead_Time_Days", "max")) | |
.reset_index()) | |
current_buffers = (df_demand[df_demand["Day"] == 1] | |
.loc[:, ["Material", "Supply_Projected"]] | |
.rename(columns={"Supply_Projected": "OnHand"})) | |
buffer_df = (demand_stats.merge(lead_times, on="Material") | |
.merge(current_buffers, on="Material", how="left")) | |
buffer_df["RecommendedBuffer"] = ( | |
Z * buffer_df["Sigma"] * np.sqrt(buffer_df["LeadTime"] + review_period) | |
).round() | |
buffer_df["Delta"] = buffer_df["RecommendedBuffer"] - buffer_df["OnHand"] | |
buffer_df["Action"] = np.where(buffer_df["Delta"] > 50, | |
"Increase buffer", | |
np.where(buffer_df["Delta"] < -50, | |
"Reduce buffer", "OK")) | |
st.subheader("📋 Buffer Recommendations") | |
display_cols = ["Material", "OnHand", "RecommendedBuffer", "Delta", "Action"] | |
st.dataframe(buffer_df[display_cols], use_container_width=True, height=300) | |
st.subheader("💰 Cost Impact Analysis") | |
carrying_cost = st.number_input("Annual Carrying Cost (% of unit cost)", min_value=0, max_value=50, value=20) | |
unit_cost = 100 | |
buffer_df["CostImpact(₹)"] = (buffer_df["Delta"] * unit_cost * (carrying_cost/100) / 12) | |
cost_chart_data = buffer_df.set_index("Material")["CostImpact(₹)"] | |
st.bar_chart(cost_chart_data) | |
st.subheader("⚡ Execute AI Recommendations") | |
for _, row in buffer_df.iterrows(): | |
if row["Action"] != "OK": | |
if st.button(f"🚀 {row['Action']} for {row['Material']}", key=row["Material"]): | |
st.success(f"AI executed: {row['Action']} - Adjusting {int(row['Delta'])} units for {row['Material']}") | |
# Performance summary | |
st.subheader("📊 Performance Summary") | |
col1, col2, col3, col4 = st.columns(4) | |
if dashboard_tab == "📊 Demand & Supply Forecast": | |
filtered_df = filtered_df_demand if 'filtered_df_demand' in locals() else df_demand | |
total_shortage_days = len(filtered_df[filtered_df['Shortfall'] > 0]) | |
critical_shortage_days = len(filtered_df[filtered_df['Shortfall'] > 30]) | |
materials_at_risk = len(filtered_df[filtered_df['Shortfall'] > 5]['Material'].unique()) | |
avg_shortfall = filtered_df['Shortfall'].mean() | |
with col1: | |
st.metric("Days with Shortages", f"{total_shortage_days}") | |
with col2: | |
st.metric("Critical Days", f"{critical_shortage_days}") | |
with col3: | |
st.metric("Materials at Risk", f"{materials_at_risk}") | |
with col4: | |
st.metric("Avg Daily Shortfall", f"{avg_shortfall:.1f} units") | |
elif dashboard_tab == "🌐 Ecosystem Supplier Impact": | |
total_suppliers_disrupted = len(df_ecosystem[df_ecosystem['Is_Disrupted'] == True]['Supplier'].unique()) | |
total_yazaki_impact_days = len(df_ecosystem[df_ecosystem['Is_Yazaki_Impacted'] == True]) | |
total_mitigation_strategies = len([s for s in st.session_state.executed_mitigations if 'eco_' in s]) | |
avg_lead_time = df_ecosystem['Lead_Time_Days'].mean() | |
with col1: | |
st.metric("Suppliers Disrupted", f"{total_suppliers_disrupted}") | |
with col2: | |
st.metric("Yazaki Impact Days", f"{total_yazaki_impact_days}") | |
with col3: | |
st.metric("Active Mitigations", f"{total_mitigation_strategies}") | |
with col4: | |
st.metric("Avg Lead Time", f"{avg_lead_time:.1f} days") | |
else: # Buffer Optimizer | |
if 'buffer_df' in locals(): | |
total_materials = len(buffer_df) | |
materials_need_increase = len(buffer_df[buffer_df['Action'] == 'Increase buffer']) | |
materials_need_decrease = len(buffer_df[buffer_df['Action'] == 'Reduce buffer']) | |
total_cost_impact = buffer_df['CostImpact(₹)'].sum() | |
with col1: | |
st.metric("Total Materials", f"{total_materials}") | |
with col2: | |
st.metric("Need Buffer Increase", f"{materials_need_increase}") | |
with col3: | |
st.metric("Need Buffer Reduction", f"{materials_need_decrease}") | |
with col4: | |
st.metric("Monthly Cost Impact", f"₹{total_cost_impact:,.0f}") | |
# Footer | |
st.markdown("---") | |
st.markdown(""" | |
<div style='text-align: center; color: #666;'> | |
<p>🌐 <b>Yazaki India Ltd 8-Week Supply Chain Command Center</b> | Firm + AI-Corrected Demand | Ecosystem Intelligence + Buffer Optimization<br> | |
Powered by Agentic AI | 8-Week Planning Horizon | Comprehensive Supply Chain Resilience</p> | |
</div> | |
""", unsafe_allow_html=True) | |