PD03 commited on
Commit
87a8746
Β·
verified Β·
1 Parent(s): 106eeca

Yazaki version

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +603 -365
src/streamlit_app.py CHANGED
@@ -1,412 +1,650 @@
 
1
  import streamlit as st
2
  import pandas as pd
 
3
  import plotly.express as px
4
  import plotly.graph_objects as go
5
  from datetime import datetime, timedelta
 
6
 
7
- # Page config
8
  st.set_page_config(
9
- page_title="Yazaki India Supply Chain Intelligence",
10
  page_icon="πŸ”Œ",
11
  layout="wide",
12
  initial_sidebar_state="expanded"
13
  )
14
 
15
- # Clean, professional CSS styling (identical to Rane version)
16
  st.markdown("""
17
  <style>
18
- /* Import modern font */
19
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
20
-
21
- /* Global styling */
22
- .stApp {
23
- font-family: 'Inter', sans-serif;
24
- background-color: #f8fafc;
25
- }
26
-
27
- /* Main container */
28
- .main-container {
29
- background: white;
30
- border-radius: 12px;
31
- padding: 2rem;
32
- margin: 1rem 0;
33
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
34
- border: 1px solid #e2e8f0;
35
- }
36
-
37
- /* Clean header */
38
- .modern-header {
39
- background: #1e293b;
40
- color: white;
41
- padding: 2rem;
42
- border-radius: 12px;
43
- margin-bottom: 2rem;
44
- border-left: 4px solid #3b82f6;
45
- }
46
-
47
- .header-title {
48
- font-size: 2rem;
49
- font-weight: 600;
50
- margin-bottom: 0.5rem;
51
- color: white;
52
- }
53
-
54
- .header-subtitle {
55
- font-size: 1rem;
56
- font-weight: 400;
57
- color: #94a3b8;
58
- }
59
-
60
- /* Clean metric cards */
61
- .metric-card {
62
- background: white;
63
- padding: 1.5rem;
64
- border-radius: 12px;
65
- border: 1px solid #e2e8f0;
66
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
67
- margin-bottom: 1rem;
68
- transition: all 0.2s ease;
69
- }
70
-
71
- .metric-card:hover {
72
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
73
- border-color: #cbd5e1;
74
- }
75
-
76
- .metric-number {
77
- font-size: 2.5rem;
78
- font-weight: 700;
79
- color: #1e293b;
80
- margin-bottom: 0.5rem;
81
- }
82
-
83
- .metric-label {
84
- color: #64748b;
85
- font-size: 0.875rem;
86
- font-weight: 500;
87
- text-transform: uppercase;
88
- letter-spacing: 0.05em;
89
- margin-bottom: 0.5rem;
90
- }
91
-
92
- .metric-change {
93
- font-size: 0.875rem;
94
- font-weight: 600;
95
- padding: 0.25rem 0.75rem;
96
- border-radius: 6px;
97
- display: inline-block;
98
- }
99
-
100
- .metric-positive {
101
- background-color: #dcfce7;
102
- color: #166534;
103
- }
104
-
105
- .metric-negative {
106
- background-color: #fef2f2;
107
- color: #dc2626;
108
- }
109
-
110
- .metric-neutral {
111
- background-color: #f1f5f9;
112
- color: #475569;
113
- }
114
-
115
- /* Clean sidebar */
116
- .sidebar-content {
117
- background: white;
118
- color: #1e293b;
119
- border-radius: 12px;
120
- padding: 1.5rem;
121
- margin-bottom: 1rem;
122
- border: 1px solid #e2e8f0;
123
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
124
- }
125
-
126
- /* Filter section */
127
- .filter-container {
128
- background: white;
129
- padding: 1.5rem;
130
- border-radius: 12px;
131
- margin-bottom: 2rem;
132
- border: 1px solid #e2e8f0;
133
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
134
- }
135
-
136
- /* Section headers */
137
- .section-header {
138
- font-size: 1.25rem;
139
- font-weight: 600;
140
- color: #1e293b;
141
- margin-bottom: 1.5rem;
142
- padding-bottom: 0.75rem;
143
- border-bottom: 2px solid #e2e8f0;
144
- }
145
-
146
- /* Status indicators */
147
- .status-indicator {
148
- display: inline-block;
149
- width: 8px;
150
- height: 8px;
151
- border-radius: 50%;
152
- margin-right: 8px;
153
- }
154
-
155
- .status-good { background-color: #22c55e; }
156
- .status-warning { background-color: #f59e0b; }
157
- .status-critical { background-color: #ef4444; }
158
-
159
- /* Clean table styling */
160
- .dataframe table {
161
- border-collapse: collapse;
162
- margin: 0;
163
- font-size: 0.875rem;
164
- width: 100%;
165
- background: white;
166
- border-radius: 8px;
167
- overflow: hidden;
168
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
169
- }
170
-
171
- .dataframe th {
172
- background-color: #f8fafc;
173
- color: #374151;
174
- font-weight: 600;
175
- padding: 12px;
176
- text-align: left;
177
- border-bottom: 1px solid #e5e7eb;
178
- }
179
-
180
- .dataframe td {
181
- padding: 12px;
182
- border-bottom: 1px solid #f3f4f6;
183
- }
184
-
185
- .dataframe tr:hover {
186
- background-color: #f9fafb;
187
- }
188
-
189
- /* Remove default streamlit styling */
190
- .stSelectbox > div > div {
191
- background-color: white;
192
- border: 1px solid #d1d5db;
193
- border-radius: 6px;
194
- }
195
-
196
- .stSelectbox > div > div:focus-within {
197
- border-color: #3b82f6;
198
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
199
- }
200
  </style>
201
  """, unsafe_allow_html=True)
202
 
203
- # Sample data functions - ONLY changed company/material names
 
 
 
 
 
 
204
  @st.cache_data
205
- def get_material_data():
206
- return pd.DataFrame({
207
- 'Material Group': ['Wire Harnesses', 'Connectors & Terminals', 'Electronic Components',
208
- 'Fuse Box Assembly', 'Junction Blocks', 'Cable Assembly', 'Power Distribution',
209
- 'Smart Sensors', 'Relay Components', 'Switch Components'],
210
- 'Current Rate': [72, 68, 65, 73, 75, 72, 78, 77, 80, 82],
211
- 'Target Rate': [75, 70, 70, 75, 78, 75, 80, 80, 82, 85],
212
- 'Trend': ['+2.1%', '+1.8%', '+0.9%', '+2.4%', '+1.2%', '+1.8%', '+2.1%', '+1.9%', '+1.1%', '+1.2%'],
213
- 'Risk Level': ['Medium', 'High', 'High', 'Low', 'Low', 'Medium', 'Low', 'Low', 'Low', 'Low'],
214
- 'Last Updated': ['2 hrs ago', '1 hr ago', '3 hrs ago', '1 hr ago', '2 hrs ago', '1 hr ago', '30 min ago', '45 min ago', '1 hr ago', '2 hrs ago']
215
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
 
217
  @st.cache_data
218
- def get_enhanced_metrics():
219
  return {
220
- 'fulfillment': 74, # Slightly lower due to import challenges
221
- 'mom_change': -1.2,
222
- 'material_groups': 89,
223
- 'skus': 8945,
224
- 'material_groups_at_risk': 24,
225
- 'risk_mom_change': 2.8,
226
- 'skus_at_risk': 31,
227
- 'sku_risk_mom_change': 3.2,
228
- 'active_suppliers': 156,
229
- 'on_time_delivery': 82.4, # Import delays impact
230
- 'import_dependency': 87
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
 
233
- # Clean, professional header - ONLY changed titles
234
- st.markdown("""
235
- <div class="modern-header">
236
- <div class="header-title">πŸ”Œ Yazaki India Supply Chain Intelligence Hub</div>
237
- <div class="header-subtitle">Real-time Import-Focused Supply Chain Dashboard β€’ Automotive Wiring Systems Control Tower</div>
238
- </div>
239
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- # Professional sidebar - ONLY changed company context
242
- with st.sidebar:
243
- st.markdown("""
244
- <div class="sidebar-content">
245
- <h3 style="margin-top: 0; color: #1e293b;">Yazaki Navigation</h3>
246
- <p style="color: #64748b; font-size: 0.875rem;">Select your workspace</p>
247
- </div>
248
- """, unsafe_allow_html=True)
249
-
250
- nav_options = [
251
- "Dashboard Home",
252
- "Import Supply Chain",
253
- "Control Tower",
254
- "Wire Harness Groups",
255
- "Global Supplier Analytics",
256
- "Import Planning",
257
- "Port & Logistics",
258
- "Import Alerts",
259
- "Compliance Center"
260
  ]
261
-
262
- selected_nav = st.selectbox("", nav_options, index=1)
263
-
264
- # Clean alerts section - ONLY changed to import context
265
- st.markdown("---")
266
- st.markdown("**Import Status**")
267
- st.error("🚒 JNPT port congestion - 4 days delay")
268
- st.warning("πŸ’± INR depreciation impacting costs")
269
- st.success("βœ… 82% customs clearance on time")
270
-
271
- # Clean filters section - ONLY changed plant names and some filters
272
- st.markdown("""
273
- <div class="filter-container">
274
- <div class="section-header">Import & Supply Chain Filters</div>
275
- </div>
276
- """, unsafe_allow_html=True)
277
-
278
- col1, col2, col3, col4 = st.columns(4)
279
- col5, col6, col7, col8 = st.columns(4)
280
-
281
- with col1:
282
- plant_location = st.selectbox("Yazaki Plant", ["Chennai (Main)", "Bawal (Haryana)", "Kanchipuram", "All Plants"], index=0)
283
 
284
- with col2:
285
- material_group = st.selectbox("Component Category", ["All Components", "Wire Harnesses", "Connectors", "Electronics"], index=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
- with col3:
288
- time_period = st.selectbox("Time Period", ["Current Quarter", "FY2025", "Last 6 Months"], index=0)
289
-
290
- with col4:
291
- supplier_tier = st.selectbox("Source Country", ["All Countries", "China", "Japan", "Germany", "South Korea"], index=0)
292
-
293
- with col5:
294
- risk_level = st.selectbox("Import Risk", ["All Risk Levels", "High Risk Only", "Medium Risk", "Low Risk"], index=0)
295
-
296
- with col6:
297
- performance = st.selectbox("Port Performance", ["All Ports", "JNPT Mumbai", "Chennai Port", "ICD Bangalore"], index=0)
298
-
299
- with col7:
300
- geography = st.selectbox("Import Mode", ["All Modes", "Sea Freight", "Air Freight", "Express"], index=0)
301
-
302
- with col8:
303
- update_freq = st.selectbox("Customs Status", ["All Status", "Cleared", "In Process", "Held"], index=0)
304
-
305
- # Get data - SAME structure
306
- material_df = get_material_data()
307
- metrics = get_enhanced_metrics()
308
-
309
- # Clean metrics section - ONLY changed one metric to import-focused
310
- st.markdown('<div class="section-header">Yazaki India Key Performance Indicators</div>', unsafe_allow_html=True)
311
-
312
- col1, col2, col3, col4 = st.columns(4)
 
 
 
 
 
 
 
 
 
 
 
313
 
314
- with col1:
315
- st.markdown(f"""
316
- <div class="metric-card">
317
- <div class="metric-number">{metrics['fulfillment']}%</div>
318
- <div class="metric-label">Overall Fulfillment</div>
319
- <div class="metric-change metric-negative">β†˜ {metrics['mom_change']}% MoM</div>
320
- </div>
321
- """, unsafe_allow_html=True)
322
 
323
- with col2:
324
- st.markdown(f"""
325
- <div class="metric-card">
326
- <div class="metric-number">{metrics['import_dependency']}%</div>
327
- <div class="metric-label">Import Dependency</div>
328
- <div class="metric-change metric-neutral">Critical Factor</div>
329
- </div>
330
- """, unsafe_allow_html=True)
331
 
332
- with col3:
333
- st.markdown(f"""
334
- <div class="metric-card">
335
- <div class="metric-number">{metrics['material_groups_at_risk']}%</div>
336
- <div class="metric-label">At-Risk Components</div>
337
- <div class="metric-change metric-negative">β†— +{metrics['risk_mom_change']}% MoM</div>
338
- </div>
339
- """, unsafe_allow_html=True)
340
 
341
- with col4:
342
- st.markdown(f"""
343
- <div class="metric-card">
344
- <div class="metric-number">{metrics['active_suppliers']:,}</div>
345
- <div class="metric-label">Global Suppliers</div>
346
- <div class="metric-change metric-neutral">+12 new</div>
 
347
  </div>
348
  """, unsafe_allow_html=True)
349
-
350
- # Clean data table - SAME structure
351
- st.markdown('<div class="section-header">Yazaki Component Group Performance</div>', unsafe_allow_html=True)
352
-
353
- # Display clean table
354
- st.dataframe(material_df, use_container_width=True, hide_index=True)
355
-
356
- # Professional charts - SAME structure, ONLY changed titles
357
- st.markdown('<div class="section-header">Import Performance Analytics</div>', unsafe_allow_html=True)
358
-
359
- col1, col2 = st.columns(2)
360
-
361
- with col1:
362
- # Clean bar chart - SAME as Rane
363
- fig1 = px.bar(
364
- material_df,
365
- x='Material Group',
366
- y=['Current Rate', 'Target Rate'],
367
- title="Fulfillment Rates: Current vs Target",
368
- color_discrete_sequence=['#3b82f6', '#64748b']
369
  )
370
 
371
- fig1.update_layout(
372
- plot_bgcolor='white',
373
- paper_bgcolor='white',
374
- font_family="Inter",
375
- title_font_size=14,
376
- title_font_color="#1e293b",
377
- xaxis_tickangle=-45,
378
- height=400,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  showlegend=True,
380
- legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
381
  )
382
 
383
- st.plotly_chart(fig1, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
- with col2:
386
- # Clean pie chart - SAME as Rane
387
- risk_counts = material_df['Risk Level'].value_counts()
388
-
389
- fig2 = px.pie(
390
- values=risk_counts.values,
391
- names=risk_counts.index,
392
- title="Risk Distribution",
393
- color_discrete_sequence=['#22c55e', '#f59e0b', '#ef4444']
 
 
 
 
 
394
  )
395
 
396
- fig2.update_layout(
397
- plot_bgcolor='white',
398
- paper_bgcolor='white',
399
- font_family="Inter",
400
- title_font_size=14,
401
- title_font_color="#1e293b",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  height=400
403
  )
404
 
405
- st.plotly_chart(fig2, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
- # Clean footer - ONLY changed company name
 
408
  st.markdown("""
409
- <div style="text-align: center; padding: 1.5rem; color: #64748b; border-top: 1px solid #e2e8f0; margin-top: 2rem; font-size: 0.875rem;">
410
- Yazaki India Dashboard last updated: {timestamp} β€’ Auto-refresh: Every 15 minutes β€’ Data accuracy: 99.7%
 
 
411
  </div>
412
- """.format(timestamp=datetime.now().strftime("%B %d, %Y at %I:%M %p")), unsafe_allow_html=True)
 
1
+ #Stable version for Yazaki India Ltd
2
  import streamlit as st
3
  import pandas as pd
4
+ import numpy as np
5
  import plotly.express as px
6
  import plotly.graph_objects as go
7
  from datetime import datetime, timedelta
8
+ import random
9
 
10
+ # Page configuration
11
  st.set_page_config(
12
+ page_title="Yazaki India Ltd - Complete Supply Chain Hub",
13
  page_icon="πŸ”Œ",
14
  layout="wide",
15
  initial_sidebar_state="expanded"
16
  )
17
 
18
+ # Custom CSS (same as before)
19
  st.markdown("""
20
  <style>
21
+ /* Add your custom CSS here */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </style>
23
  """, unsafe_allow_html=True)
24
 
25
+ # Initialize session state
26
+ if 'executed_mitigations' not in st.session_state:
27
+ st.session_state.executed_mitigations = []
28
+ if 'external_signals' not in st.session_state:
29
+ st.session_state.external_signals = []
30
+
31
+ # UPDATED: Generate 8-week forward-looking demand data for Yazaki
32
  @st.cache_data
33
+ def generate_8week_demand_data():
34
+ today = datetime(2025, 8, 4)
35
+ dates = [today + timedelta(days=x) for x in range(56)] # 8 weeks = 56 days
36
+
37
+ # Yazaki-specific materials (wire harnesses, connectors, electrical components)
38
+ materials = [
39
+ 'WH001-Engine Wire Harness',
40
+ 'WH002-Dashboard Wire Harness',
41
+ 'CON001-Electrical Connector',
42
+ 'TER001-Wire Terminal',
43
+ 'FUS001-Fuse Box Assembly'
44
+ ]
45
+
46
+ all_data = []
47
+ for material in materials:
48
+ np.random.seed(hash(material) % 1000)
49
+
50
+ # Generate base demand patterns
51
+ base_demand = np.random.normal(150, 15, 56)
52
+
53
+ # First 14 days: FIRM DEMAND
54
+ firm_demand = np.clip(base_demand[:14], 100, 200).astype(int)
55
+
56
+ # Days 15-56: Customer shared demand (tentative)
57
+ customer_shared = np.clip(base_demand[14:] * (1 + 0.05 * np.sin(np.linspace(0, 3.14, 42))), 80, 220).astype(int)
58
+
59
+ # Days 15-56: AI-corrected demand (with external signals)
60
+ external_factors = np.zeros(42)
61
+ # Weather impact (weeks 3-4)
62
+ external_factors[0:14] += np.random.normal(0, 5, 14)
63
+ # EV policy impact (weeks 5-8) - higher for wire harnesses
64
+ if 'WH' in material:
65
+ external_factors[14:] += 12 # Higher impact for wire harnesses in EVs
66
+ elif 'CON' in material or 'TER' in material:
67
+ external_factors[14:] += 8 # Moderate impact for connectors/terminals
68
+ # Festive season boost (weeks 6-7)
69
+ external_factors[28:42] += 8
70
+
71
+ corrected_demand = np.clip(customer_shared + external_factors, 60, 250).astype(int)
72
+
73
+ # Generate supply plan for 56 days
74
+ supply_capacity = np.random.normal(155, 12, 56)
75
+ supply_plan = np.clip(supply_capacity, 120, 220).astype(int)
76
+
77
+ # Apply disruptions to supply (weather impact on days 15-18)
78
+ supply_actual = supply_plan.copy()
79
+ supply_actual[15:19] = (supply_actual[15:19] * 0.8).astype(int)
80
+
81
+ for i, date in enumerate(dates):
82
+ # Determine which demand to use
83
+ if i < 14:
84
+ demand_used = firm_demand[i]
85
+ firm_val = firm_demand[i]
86
+ customer_val = None
87
+ corrected_val = None
88
+ demand_type = "Firm"
89
+ else:
90
+ demand_used = corrected_demand[i-14]
91
+ firm_val = None
92
+ customer_val = customer_shared[i-14]
93
+ corrected_val = corrected_demand[i-14]
94
+ demand_type = "AI-Corrected"
95
+
96
+ # Calculate shortfall
97
+ shortfall = max(0, demand_used - supply_actual[i])
98
+
99
+ all_data.append({
100
+ 'Date': date,
101
+ 'Week': f"Week {(i//7)+1}",
102
+ 'Day': i + 1,
103
+ 'Material': material,
104
+ 'Firm_Demand': firm_val,
105
+ 'Customer_Demand': customer_val,
106
+ 'Corrected_Demand': corrected_val,
107
+ 'Demand_Used': demand_used,
108
+ 'Supply_Plan': supply_plan[i],
109
+ 'Supply_Projected': supply_actual[i],
110
+ 'Shortfall': shortfall,
111
+ 'Demand_Type': demand_type,
112
+ 'Gap': supply_actual[i] - demand_used
113
+ })
114
+
115
+ return pd.DataFrame(all_data)
116
 
117
+ # Yazaki-specific Tier-2 suppliers
118
  @st.cache_data
119
+ def get_tier2_suppliers():
120
  return {
121
+ 'Furukawa Electric India': {
122
+ 'location': 'Chennai',
123
+ 'materials': ['WH001-Engine Wire Harness', 'WH002-Dashboard Wire Harness'],
124
+ 'capacity': 200,
125
+ 'reliability': 95,
126
+ 'lead_time': 2,
127
+ 'risk_factors': ['Monsoon flooding', 'Port congestion', 'Copper price volatility']
128
+ },
129
+ 'Sumitomo Wiring Systems': {
130
+ 'location': 'Bangalore',
131
+ 'materials': ['CON001-Electrical Connector', 'TER001-Wire Terminal'],
132
+ 'capacity': 180,
133
+ 'reliability': 92,
134
+ 'lead_time': 3,
135
+ 'risk_factors': ['Transportation delays', 'Raw material shortage', 'Equipment failure']
136
+ },
137
+ 'JST India Private Limited': {
138
+ 'location': 'Pune',
139
+ 'materials': ['FUS001-Fuse Box Assembly', 'CON001-Electrical Connector'],
140
+ 'capacity': 220,
141
+ 'reliability': 88,
142
+ 'lead_time': 1,
143
+ 'risk_factors': ['Quality issues', 'Capacity constraints', 'Supplier disputes']
144
+ }
145
  }
146
 
147
+ # Keep existing ecosystem generation (updated with Yazaki suppliers)
148
+ @st.cache_data
149
+ def generate_ecosystem_data():
150
+ today = datetime(2025, 8, 4)
151
+ dates = [today + timedelta(days=x) for x in range(14)]
152
+ suppliers = get_tier2_suppliers()
153
+
154
+ all_data = []
155
+ for supplier_name, supplier_info in suppliers.items():
156
+ for material in supplier_info['materials']:
157
+ np.random.seed(hash(supplier_name + material) % 1000)
158
+
159
+ base_capacity = supplier_info['capacity']
160
+ normal_supply = np.full(14, base_capacity, dtype=int)
161
+ disrupted_supply = normal_supply.copy()
162
+
163
+ if supplier_name == 'Furukawa Electric India':
164
+ disrupted_supply[3:7] = (disrupted_supply[3:7] * 0.3).astype(int)
165
+ disruption_cause = "Monsoon flooding in Chennai"
166
+ disruption_days = list(range(3, 7))
167
+ elif supplier_name == 'Sumitomo Wiring Systems':
168
+ disrupted_supply[5:8] = (disrupted_supply[5:8] * 0.5).astype(int)
169
+ disruption_cause = "Critical equipment failure"
170
+ disruption_days = list(range(5, 8))
171
+ elif supplier_name == 'JST India Private Limited':
172
+ disrupted_supply[8:11] = (disrupted_supply[8:11] * 0.2).astype(int)
173
+ disruption_cause = "Labor strike at Pune facility"
174
+ disruption_days = list(range(8, 11))
175
+ else:
176
+ disruption_cause = "No disruption"
177
+ disruption_days = []
178
+
179
+ lead_time = supplier_info['lead_time']
180
+ yazaki_supply = np.full(14, base_capacity, dtype=int)
181
+
182
+ for disruption_day in disruption_days:
183
+ arrival_day = disruption_day + lead_time
184
+ if arrival_day < 14:
185
+ reduction = normal_supply[disruption_day] - disrupted_supply[disruption_day]
186
+ yazaki_supply[arrival_day] = max(yazaki_supply[arrival_day] - reduction, 0)
187
+
188
+ for i, date in enumerate(dates):
189
+ all_data.append({
190
+ 'Date': date,
191
+ 'Supplier': supplier_name,
192
+ 'Material': material,
193
+ 'Tier2_Normal_Supply': int(normal_supply[i]),
194
+ 'Tier2_Disrupted_Supply': int(disrupted_supply[i]),
195
+ 'Tier2_Impact': int(normal_supply[i] - disrupted_supply[i]),
196
+ 'Yazaki_Normal_Supply': int(normal_supply[i]),
197
+ 'Yazaki_Impacted_Supply': int(yazaki_supply[i]),
198
+ 'Yazaki_Impact': int(normal_supply[i] - yazaki_supply[i]),
199
+ 'Disruption_Cause': disruption_cause if i in disruption_days else "Normal Operations",
200
+ 'Lead_Time_Days': lead_time,
201
+ 'Is_Disrupted': i in disruption_days,
202
+ 'Is_Yazaki_Impacted': yazaki_supply[i] < normal_supply[i]
203
+ })
204
+
205
+ return pd.DataFrame(all_data)
206
 
207
+ # Updated external signals for Yazaki
208
+ @st.cache_data
209
+ def get_external_signals():
210
+ return [
211
+ {'Source': 'Weather API', 'Signal': 'Heavy rains forecasted in Chennai for next 3 days', 'Impact': 'Supply Risk', 'Confidence': 95},
212
+ {'Source': 'Market Intelligence', 'Signal': 'EV sales up 25% this quarter - increased wire harness demand', 'Impact': 'Demand Increase', 'Confidence': 88},
213
+ {'Source': 'News Analytics', 'Signal': 'Upcoming festive season - historically 15% automotive demand spike', 'Impact': 'Demand Surge', 'Confidence': 92},
214
+ {'Source': 'Supplier Network', 'Signal': 'Tier-2 connector supplier capacity increased by 20%', 'Impact': 'Supply Boost', 'Confidence': 98},
215
+ {'Source': 'Social Media', 'Signal': 'Positive sentiment around new Tata EV models', 'Impact': 'Demand Growth', 'Confidence': 75},
216
+ {'Source': 'Government Portal', 'Signal': 'New EV subsidy policy effective next week', 'Impact': 'Market Expansion', 'Confidence': 100}
 
 
 
 
 
 
 
 
 
217
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ # UPDATED: Generate alerts for 8-week data
220
+ def generate_detailed_alerts(df):
221
+ alerts = []
222
+ for material in df['Material'].unique():
223
+ material_data = df[df['Material'] == material]
224
+ shortage_days = material_data[material_data['Shortfall'] > 5]
225
+
226
+ if not shortage_days.empty:
227
+ for _, row in shortage_days.iterrows():
228
+ root_causes = []
229
+
230
+ if row['Day'] > 14:
231
+ if row['Corrected_Demand'] and row['Customer_Demand']:
232
+ diff = row['Corrected_Demand'] - row['Customer_Demand']
233
+ if diff > 10:
234
+ root_causes.append(f"AI detected {diff} units additional demand from external signals")
235
+
236
+ if row['Day'] >= 15 and row['Day'] <= 18:
237
+ root_causes.append("Chennai plant weather disruption reducing supply")
238
+ else:
239
+ root_causes.append("Firm demand exceeding supply capacity")
240
+
241
+ if not root_causes:
242
+ root_causes.append("Base demand exceeding current supply capacity")
243
+
244
+ mitigation_options = [
245
+ {"option": "Activate Aurangabad backup production", "impact": "+30 units/day", "cost": "High", "timeline": "24 hours"},
246
+ {"option": "Expedite Tier-2 supplier shipments", "impact": "+15 units/day", "cost": "Medium", "timeline": "12 hours"},
247
+ {"option": "Emergency air freight from Yazaki Philippines", "impact": "+40 units/day", "cost": "Very High", "timeline": "6 hours"},
248
+ {"option": "Reallocate inventory from other Yazaki plants", "impact": "+20 units/day", "cost": "Low", "timeline": "18 hours"}
249
+ ]
250
+
251
+ if row['Shortfall'] > 30:
252
+ best_option = mitigation_options[2]
253
+ elif row['Shortfall'] > 15:
254
+ best_option = mitigation_options[0]
255
+ else:
256
+ best_option = mitigation_options[1]
257
+
258
+ alerts.append({
259
+ 'material': material,
260
+ 'date': row['Date'].strftime('%Y-%m-%d'),
261
+ 'week': row['Week'],
262
+ 'shortage': int(row['Shortfall']),
263
+ 'demand_type': row['Demand_Type'],
264
+ 'severity': 'Critical' if row['Shortfall'] > 30 else 'High' if row['Shortfall'] > 15 else 'Medium',
265
+ 'root_causes': root_causes,
266
+ 'mitigation_options': mitigation_options,
267
+ 'best_option': best_option
268
+ })
269
+
270
+ return alerts
271
 
272
+ # Keep mitigation strategies with Yazaki-specific updates
273
+ def generate_mitigation_strategies(supplier, material, impact_amount, impact_days):
274
+ base_strategies = [
275
+ {
276
+ 'strategy': 'Activate Alternate Supplier',
277
+ 'description': f'Engage backup supplier for {material}',
278
+ 'timeline': '24-48 hours',
279
+ 'cost': 'High (+15% unit cost)',
280
+ 'effectiveness': '90%',
281
+ 'capacity': f'+{impact_amount * 0.9:.0f} units/day',
282
+ },
283
+ {
284
+ 'strategy': 'Emergency Air Freight',
285
+ 'description': f'Air freight {material} from Yazaki global network',
286
+ 'timeline': '6-12 hours',
287
+ 'cost': 'Very High (+40% logistics cost)',
288
+ 'effectiveness': '75%',
289
+ 'capacity': f'+{impact_amount * 0.75:.0f} units/day',
290
+ },
291
+ {
292
+ 'strategy': 'Inventory Reallocation',
293
+ 'description': f'Reallocate {material} from other Yazaki plants',
294
+ 'timeline': '12-24 hours',
295
+ 'cost': 'Medium (+5% handling cost)',
296
+ 'effectiveness': '60%',
297
+ 'capacity': f'+{impact_amount * 0.6:.0f} units/day',
298
+ }
299
+ ]
300
+
301
+ if impact_amount > 100:
302
+ recommended = [0, 1]
303
+ elif impact_amount > 50:
304
+ recommended = [0, 2]
305
+ else:
306
+ recommended = [2]
307
+
308
+ return base_strategies, recommended
309
 
310
+ # Load data
311
+ df_demand = generate_8week_demand_data()
312
+ df_ecosystem = generate_ecosystem_data()
313
+ external_signals = get_external_signals()
314
+ suppliers = get_tier2_suppliers()
 
 
 
315
 
316
+ # Simple title
317
+ st.title("Yazaki India Ltd - Supply Chain Command Center")
 
 
 
 
 
 
318
 
319
+ # Tab Navigation
320
+ st.sidebar.title("🎯 Dashboard Navigation")
321
+ dashboard_tab = st.sidebar.radio(
322
+ "Select Dashboard:",
323
+ ["πŸ“Š Demand & Supply Forecast", "🌐 Ecosystem Supplier Impact", "πŸ›‘οΈ Buffer Optimizer"],
324
+ index=0
325
+ )
 
326
 
327
+ # UPDATED TAB 1: 8-WEEK DEMAND & SUPPLY FORECAST
328
+ if dashboard_tab == "πŸ“Š Demand & Supply Forecast":
329
+ st.markdown("""
330
+ <div style="background: linear-gradient(135deg, #1f4e79, #2d5aa0); padding: 20px; border-radius: 15px; margin-bottom: 20px;">
331
+ <h2 style="color: white; margin: 0; text-align: center;">
332
+ πŸ”Œ 8-Week Planning Horizon | Firm Demand (Days 1-14) | AI-Corrected Demand (Days 15-56)
333
+ </h2>
334
  </div>
335
  """, unsafe_allow_html=True)
336
+
337
+ # Material selection
338
+ selected_material = st.selectbox(
339
+ "Select Material for Analysis:",
340
+ df_demand['Material'].unique(),
341
+ key="forecast_material"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  )
343
 
344
+ # Filter data
345
+ material_df = df_demand[df_demand['Material'] == selected_material].copy()
346
+
347
+ # Create visualization
348
+ fig = go.Figure()
349
+
350
+ # Add firm demand (days 1-14)
351
+ firm_data = material_df[material_df['Demand_Type'] == 'Firm']
352
+ fig.add_trace(go.Scatter(
353
+ x=firm_data['Date'],
354
+ y=firm_data['Demand_Used'],
355
+ mode='lines+markers',
356
+ name='Firm Demand (Days 1-14)',
357
+ line=dict(color='#2E86AB', width=3),
358
+ marker=dict(size=8)
359
+ ))
360
+
361
+ # Add AI-corrected demand (days 15-56)
362
+ corrected_data = material_df[material_df['Demand_Type'] == 'AI-Corrected']
363
+ fig.add_trace(go.Scatter(
364
+ x=corrected_data['Date'],
365
+ y=corrected_data['Demand_Used'],
366
+ mode='lines+markers',
367
+ name='AI-Corrected Demand (Days 15-56)',
368
+ line=dict(color='#A23B72', width=3, dash='dot'),
369
+ marker=dict(size=6)
370
+ ))
371
+
372
+ # Add supply projection
373
+ fig.add_trace(go.Scatter(
374
+ x=material_df['Date'],
375
+ y=material_df['Supply_Projected'],
376
+ mode='lines+markers',
377
+ name='Supply Projection',
378
+ line=dict(color='#F18F01', width=2),
379
+ marker=dict(size=6)
380
+ ))
381
+
382
+ # Highlight shortage areas
383
+ shortage_data = material_df[material_df['Shortfall'] > 0]
384
+ if not shortage_data.empty:
385
+ fig.add_trace(go.Scatter(
386
+ x=shortage_data['Date'],
387
+ y=shortage_data['Shortfall'],
388
+ mode='markers',
389
+ name='Shortage Alert',
390
+ marker=dict(color='red', size=10, symbol='triangle-up'),
391
+ yaxis='y2'
392
+ ))
393
+
394
+ fig.update_layout(
395
+ title=f"8-Week Demand & Supply Forecast - {selected_material}",
396
+ xaxis_title="Date",
397
+ yaxis_title="Units",
398
+ yaxis2=dict(title="Shortage", overlaying='y', side='right'),
399
+ height=600,
400
  showlegend=True,
401
+ hovermode='x unified'
402
  )
403
 
404
+ st.plotly_chart(fig, use_container_width=True)
405
+
406
+ # Generate and display alerts
407
+ alerts = generate_detailed_alerts(df_demand)
408
+
409
+ if alerts:
410
+ st.markdown("""
411
+ <div style="background: #ffe6e6; padding: 15px; border-radius: 10px; border-left: 5px solid #ff4444; margin: 20px 0;">
412
+ <h3 style="color: #cc0000; margin-top: 0;">⚠️ Critical Supply Chain Alerts</h3>
413
+ </div>
414
+ """, unsafe_allow_html=True)
415
+
416
+ for alert in alerts[:3]: # Show top 3 alerts
417
+ severity_color = {'Critical': '#ff4444', 'High': '#ff8800', 'Medium': '#ffcc00'}[alert['severity']]
418
+
419
+ st.markdown(f"""
420
+ <div style="background: white; padding: 15px; border-radius: 10px; border-left: 5px solid {severity_color}; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
421
+ <h4 style="color: {severity_color}; margin: 0 0 10px 0;">
422
+ 🚨 {alert['severity']} Alert: {alert['material']}
423
+ </h4>
424
+ <p style="margin: 5px 0;">
425
+ <strong>Date:</strong> {alert['date']} ({alert['week']}) |
426
+ <strong>Shortage:</strong> {alert['shortage']} units |
427
+ <strong>Type:</strong> {alert['demand_type']}
428
+ </p>
429
+ <p style="margin: 5px 0;"><strong>Root Causes:</strong></p>
430
+ <ul style="margin: 5px 0;">
431
+ {''.join([f"<li>{cause}</li>" for cause in alert['root_causes']])}
432
+ </ul>
433
+ <p style="margin: 10px 0 5px 0;"><strong>🎯 Recommended Action:</strong></p>
434
+ <div style="background: #f0f8ff; padding: 10px; border-radius: 5px;">
435
+ <strong>{alert['best_option']['option']}</strong><br>
436
+ Impact: {alert['best_option']['impact']} | Cost: {alert['best_option']['cost']} | Timeline: {alert['best_option']['timeline']}
437
+ </div>
438
+ </div>
439
+ """, unsafe_allow_html=True)
440
 
441
+ elif dashboard_tab == "🌐 Ecosystem Supplier Impact":
442
+ st.markdown("""
443
+ <div style="background: linear-gradient(135deg, #1f4e79, #2d5aa0); padding: 20px; border-radius: 15px; margin-bottom: 20px;">
444
+ <h2 style="color: white; margin: 0; text-align: center;">
445
+ 🌐 Tier 2 Supplier Disruption Analysis | Cascading Impact Modeling | Automated Mitigation Response
446
+ </h2>
447
+ </div>
448
+ """, unsafe_allow_html=True)
449
+
450
+ # Supplier selection
451
+ selected_supplier = st.selectbox(
452
+ "Select Tier-2 Supplier for Analysis:",
453
+ df_ecosystem['Supplier'].unique(),
454
+ key="ecosystem_supplier"
455
  )
456
 
457
+ # Filter and analyze
458
+ supplier_df = df_ecosystem[df_ecosystem['Supplier'] == selected_supplier]
459
+
460
+ col1, col2 = st.columns(2)
461
+
462
+ with col1:
463
+ # Supplier disruption chart
464
+ fig1 = go.Figure()
465
+
466
+ for material in supplier_df['Material'].unique():
467
+ material_data = supplier_df[supplier_df['Material'] == material]
468
+
469
+ fig1.add_trace(go.Scatter(
470
+ x=material_data['Date'],
471
+ y=material_data['Tier2_Normal_Supply'],
472
+ mode='lines',
473
+ name=f'{material} - Normal',
474
+ line=dict(width=2)
475
+ ))
476
+
477
+ fig1.add_trace(go.Scatter(
478
+ x=material_data['Date'],
479
+ y=material_data['Tier2_Disrupted_Supply'],
480
+ mode='lines',
481
+ name=f'{material} - Disrupted',
482
+ line=dict(dash='dot', width=2)
483
+ ))
484
+
485
+ fig1.update_layout(
486
+ title=f"Tier-2 Supplier Impact: {selected_supplier}",
487
+ xaxis_title="Date",
488
+ yaxis_title="Supply Units",
489
+ height=400
490
+ )
491
+
492
+ st.plotly_chart(fig1, use_container_width=True)
493
+
494
+ with col2:
495
+ # Yazaki impact chart
496
+ fig2 = go.Figure()
497
+
498
+ for material in supplier_df['Material'].unique():
499
+ material_data = supplier_df[supplier_df['Material'] == material]
500
+
501
+ fig2.add_trace(go.Scatter(
502
+ x=material_data['Date'],
503
+ y=material_data['Yazaki_Normal_Supply'],
504
+ mode='lines',
505
+ name=f'{material} - Normal',
506
+ line=dict(width=2)
507
+ ))
508
+
509
+ fig2.add_trace(go.Scatter(
510
+ x=material_data['Date'],
511
+ y=material_data['Yazaki_Impacted_Supply'],
512
+ mode='lines',
513
+ name=f'{material} - Impacted',
514
+ line=dict(dash='dot', width=2)
515
+ ))
516
+
517
+ fig2.update_layout(
518
+ title=f"Cascading Impact on Yazaki Production",
519
+ xaxis_title="Date",
520
+ yaxis_title="Supply Units",
521
+ height=400
522
+ )
523
+
524
+ st.plotly_chart(fig2, use_container_width=True)
525
+
526
+ # Disruption alerts
527
+ disrupted_days = supplier_df[supplier_df['Is_Disrupted'] == True]
528
+
529
+ if not disrupted_days.empty:
530
+ for _, alert_day in disrupted_days.iterrows():
531
+ if alert_day['Tier2_Impact'] > 0:
532
+ strategies, recommended = generate_mitigation_strategies(
533
+ alert_day['Supplier'],
534
+ alert_day['Material'],
535
+ alert_day['Tier2_Impact'],
536
+ 1
537
+ )
538
+
539
+ st.markdown(f"""
540
+ <div style="background: white; padding: 15px; border-radius: 10px; border-left: 5px solid #ff8800; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
541
+ <h4 style="color: #ff8800; margin: 0 0 10px 0;">
542
+ 🏭 Supplier Disruption Alert
543
+ </h4>
544
+ <p style="margin: 5px 0;">
545
+ <strong>Supplier:</strong> {alert_day['Supplier']} |
546
+ <strong>Material:</strong> {alert_day['Material']}
547
+ </p>
548
+ <p style="margin: 5px 0;">
549
+ <strong>Root Cause:</strong> {alert_day['Disruption_Cause']}
550
+ </p>
551
+ <p style="margin: 5px 0;">
552
+ <strong>Impact:</strong> -{alert_day['Tier2_Impact']} units |
553
+ <strong>Yazaki Impact:</strong> -{alert_day['Yazaki_Impact']} units (after {alert_day['Lead_Time_Days']} days)
554
+ </p>
555
+ </div>
556
+ """, unsafe_allow_html=True)
557
+
558
+ break # Show only first alert to avoid repetition
559
+
560
+ elif dashboard_tab == "πŸ›‘οΈ Buffer Optimizer":
561
+ st.markdown("""
562
+ <div style="background: linear-gradient(135deg, #1f4e79, #2d5aa0); padding: 20px; border-radius: 15px; margin-bottom: 20px;">
563
+ <h2 style="color: white; margin: 0; text-align: center;">
564
+ πŸ›‘οΈ AI-driven safety-stock recommendations across the full network
565
+ </h2>
566
+ </div>
567
+ """, unsafe_allow_html=True)
568
+
569
+ # Buffer optimization logic (simplified)
570
+ buffer_data = []
571
+ for material in df_demand['Material'].unique():
572
+ material_data = df_demand[df_demand['Material'] == material]
573
+ avg_demand = material_data['Demand_Used'].mean()
574
+ demand_volatility = material_data['Demand_Used'].std()
575
+ max_shortfall = material_data['Shortfall'].max()
576
+
577
+ # Calculate recommended buffer
578
+ base_buffer = avg_demand * 0.15 # 15% base buffer
579
+ volatility_buffer = demand_volatility * 1.5
580
+ risk_buffer = max_shortfall * 0.8
581
+
582
+ recommended_buffer = int(base_buffer + volatility_buffer + risk_buffer)
583
+ current_buffer = int(avg_demand * 0.10) # Assume current is 10%
584
+
585
+ buffer_data.append({
586
+ 'Material': material,
587
+ 'Current_Buffer': current_buffer,
588
+ 'Recommended_Buffer': recommended_buffer,
589
+ 'Gap': recommended_buffer - current_buffer,
590
+ 'Cost_Impact': f"β‚Ή{(recommended_buffer - current_buffer) * 150:,}", # Assuming β‚Ή150 per unit
591
+ 'Risk_Reduction': f"{min(90, max_shortfall * 2):.0f}%"
592
+ })
593
+
594
+ buffer_df = pd.DataFrame(buffer_data)
595
+
596
+ # Display buffer recommendations
597
+ st.subheader("πŸ“Š Safety Stock Recommendations")
598
+
599
+ fig = go.Figure()
600
+
601
+ fig.add_trace(go.Bar(
602
+ name='Current Buffer',
603
+ x=buffer_df['Material'],
604
+ y=buffer_df['Current_Buffer'],
605
+ marker_color='lightblue'
606
+ ))
607
+
608
+ fig.add_trace(go.Bar(
609
+ name='Recommended Buffer',
610
+ x=buffer_df['Material'],
611
+ y=buffer_df['Recommended_Buffer'],
612
+ marker_color='orange'
613
+ ))
614
+
615
+ fig.update_layout(
616
+ title="Current vs Recommended Safety Stock Levels",
617
+ xaxis_title="Materials",
618
+ yaxis_title="Buffer Stock Units",
619
+ barmode='group',
620
  height=400
621
  )
622
 
623
+ st.plotly_chart(fig, use_container_width=True)
624
+
625
+ # Buffer recommendations table
626
+ st.subheader("πŸ“‹ Detailed Buffer Analysis")
627
+ st.dataframe(buffer_df, use_container_width=True)
628
+
629
+ # External Signals Panel
630
+ st.sidebar.markdown("---")
631
+ st.sidebar.subheader("🌍 External Market Signals")
632
+ for signal in external_signals[:3]:
633
+ confidence_color = "green" if signal['Confidence'] > 90 else "orange" if signal['Confidence'] > 80 else "red"
634
+ st.sidebar.markdown(f"""
635
+ <div style="background: white; padding: 8px; border-radius: 8px; margin: 8px 0; border-left: 3px solid {confidence_color};">
636
+ <strong style="color: {confidence_color};">{signal['Source']}</strong><br>
637
+ <small>{signal['Signal']}</small><br>
638
+ <small><strong>Impact:</strong> {signal['Impact']} ({signal['Confidence']}%)</small>
639
+ </div>
640
+ """, unsafe_allow_html=True)
641
 
642
+ # Footer
643
+ st.markdown("---")
644
  st.markdown("""
645
+ <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #1f4e79, #2d5aa0); border-radius: 15px; color: white;">
646
+ <h3>πŸ”Œ Yazaki India Ltd 8-Week Supply Chain Command Center</h3>
647
+ <p><strong>Firm + AI-Corrected Demand | Ecosystem Intelligence + Buffer Optimization</strong></p>
648
+ <p><em>Powered by Agentic AI | 8-Week Planning Horizon | Comprehensive Supply Chain Resilience</em></p>
649
  </div>
650
+ """, unsafe_allow_html=True)