|
import streamlit as st |
|
import pandas as pd |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
from datetime import datetime, timedelta |
|
|
|
|
|
st.set_page_config( |
|
page_title="Supply Chain Intelligence", |
|
page_icon="π", |
|
layout="wide", |
|
initial_sidebar_state="expanded" |
|
) |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
/* Import modern font */ |
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
|
|
|
/* Global styling */ |
|
.stApp { |
|
font-family: 'Inter', sans-serif; |
|
background-color: #f8fafc; |
|
} |
|
|
|
/* Main container */ |
|
.main-container { |
|
background: white; |
|
border-radius: 12px; |
|
padding: 2rem; |
|
margin: 1rem 0; |
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
border: 1px solid #e2e8f0; |
|
} |
|
|
|
/* Clean header */ |
|
.modern-header { |
|
background: #1e293b; |
|
color: white; |
|
padding: 2rem; |
|
border-radius: 12px; |
|
margin-bottom: 2rem; |
|
border-left: 4px solid #3b82f6; |
|
} |
|
|
|
.header-title { |
|
font-size: 2rem; |
|
font-weight: 600; |
|
margin-bottom: 0.5rem; |
|
color: white; |
|
} |
|
|
|
.header-subtitle { |
|
font-size: 1rem; |
|
font-weight: 400; |
|
color: #94a3b8; |
|
} |
|
|
|
/* Clean metric cards */ |
|
.metric-card { |
|
background: white; |
|
padding: 1.5rem; |
|
border-radius: 12px; |
|
border: 1px solid #e2e8f0; |
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
margin-bottom: 1rem; |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.metric-card:hover { |
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|
border-color: #cbd5e1; |
|
} |
|
|
|
.metric-number { |
|
font-size: 2.5rem; |
|
font-weight: 700; |
|
color: #1e293b; |
|
margin-bottom: 0.5rem; |
|
} |
|
|
|
.metric-label { |
|
color: #64748b; |
|
font-size: 0.875rem; |
|
font-weight: 500; |
|
text-transform: uppercase; |
|
letter-spacing: 0.05em; |
|
margin-bottom: 0.5rem; |
|
} |
|
|
|
.metric-change { |
|
font-size: 0.875rem; |
|
font-weight: 600; |
|
padding: 0.25rem 0.75rem; |
|
border-radius: 6px; |
|
display: inline-block; |
|
} |
|
|
|
.metric-positive { |
|
background-color: #dcfce7; |
|
color: #166534; |
|
} |
|
|
|
.metric-negative { |
|
background-color: #fef2f2; |
|
color: #dc2626; |
|
} |
|
|
|
.metric-neutral { |
|
background-color: #f1f5f9; |
|
color: #475569; |
|
} |
|
|
|
/* Clean sidebar */ |
|
.sidebar-content { |
|
background: white; |
|
color: #1e293b; |
|
border-radius: 12px; |
|
padding: 1.5rem; |
|
margin-bottom: 1rem; |
|
border: 1px solid #e2e8f0; |
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
/* Filter section */ |
|
.filter-container { |
|
background: white; |
|
padding: 1.5rem; |
|
border-radius: 12px; |
|
margin-bottom: 2rem; |
|
border: 1px solid #e2e8f0; |
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
/* Section headers */ |
|
.section-header { |
|
font-size: 1.25rem; |
|
font-weight: 600; |
|
color: #1e293b; |
|
margin-bottom: 1.5rem; |
|
padding-bottom: 0.75rem; |
|
border-bottom: 2px solid #e2e8f0; |
|
} |
|
|
|
/* Status indicators */ |
|
.status-indicator { |
|
display: inline-block; |
|
width: 8px; |
|
height: 8px; |
|
border-radius: 50%; |
|
margin-right: 8px; |
|
} |
|
|
|
.status-good { background-color: #22c55e; } |
|
.status-warning { background-color: #f59e0b; } |
|
.status-critical { background-color: #ef4444; } |
|
|
|
/* Clean table styling */ |
|
.dataframe table { |
|
border-collapse: collapse; |
|
margin: 0; |
|
font-size: 0.875rem; |
|
width: 100%; |
|
background: white; |
|
border-radius: 8px; |
|
overflow: hidden; |
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.dataframe th { |
|
background-color: #f8fafc; |
|
color: #374151; |
|
font-weight: 600; |
|
padding: 12px; |
|
text-align: left; |
|
border-bottom: 1px solid #e5e7eb; |
|
} |
|
|
|
.dataframe td { |
|
padding: 12px; |
|
border-bottom: 1px solid #f3f4f6; |
|
} |
|
|
|
.dataframe tr:hover { |
|
background-color: #f9fafb; |
|
} |
|
|
|
/* Remove default streamlit styling */ |
|
.stSelectbox > div > div { |
|
background-color: white; |
|
border: 1px solid #d1d5db; |
|
border-radius: 6px; |
|
} |
|
|
|
.stSelectbox > div > div:focus-within { |
|
border-color: #3b82f6; |
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
@st.cache_data |
|
def get_material_data(): |
|
return pd.DataFrame({ |
|
'Material Group': ['Engine Components', 'Hydraulic Systems', 'Interior & Seating', |
|
'Battery Systems', 'Suspension Parts', 'Safety Components', 'Semiconductors', |
|
'Smart Sensors', 'Brake Systems', 'Shock Absorbers'], |
|
'Current Rate': [72, 68, 65, 73, 75, 72, 78, 77, 80, 82], |
|
'Target Rate': [75, 70, 70, 75, 78, 75, 80, 80, 82, 85], |
|
'Trend': ['+2.1%', '+1.8%', '+0.9%', '+2.4%', '+1.2%', '+1.8%', '+2.1%', '+1.9%', '+1.1%', '+1.2%'], |
|
'Risk Level': ['Medium', 'High', 'High', 'Low', 'Low', 'Medium', 'Low', 'Low', 'Low', 'Low'], |
|
'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'] |
|
}) |
|
|
|
@st.cache_data |
|
def get_enhanced_metrics(): |
|
return { |
|
'fulfillment': 86, |
|
'mom_change': 2.3, |
|
'material_groups': 147, |
|
'skus': 12847, |
|
'material_groups_at_risk': 18, |
|
'risk_mom_change': -1.8, |
|
'skus_at_risk': 23, |
|
'sku_risk_mom_change': -2.1, |
|
'active_suppliers': 342, |
|
'on_time_delivery': 94.2 |
|
} |
|
|
|
|
|
st.markdown(""" |
|
<div class="modern-header"> |
|
<div class="header-title">Supply Chain Intelligence Hub</div> |
|
<div class="header-subtitle">Real-time Supply Chain Resilience Dashboard β’ Control Tower Analytics</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with st.sidebar: |
|
st.markdown(""" |
|
<div class="sidebar-content"> |
|
<h3 style="margin-top: 0; color: #1e293b;">Navigation</h3> |
|
<p style="color: #64748b; font-size: 0.875rem;">Select your workspace</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
nav_options = [ |
|
"Dashboard Home", |
|
"Supply Chain Resilience", |
|
"Control Tower", |
|
"Material Groups", |
|
"Supplier Analytics", |
|
"Demand Planning", |
|
"Insights & Trends", |
|
"Real-time Alerts", |
|
"Reports Center" |
|
] |
|
|
|
selected_nav = st.selectbox("", nav_options, index=1) |
|
|
|
|
|
st.markdown("---") |
|
st.markdown("**System Status**") |
|
st.error("β οΈ 3 suppliers need attention") |
|
st.warning("π 12 SKUs below safety stock") |
|
st.success("β
94% on-time delivery") |
|
|
|
|
|
st.markdown(""" |
|
<div class="filter-container"> |
|
<div class="section-header">Filters & Controls</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
col5, col6, col7, col8 = st.columns(4) |
|
|
|
with col1: |
|
plant_location = st.selectbox("Plant Location", ["Chennai Hub", "Mumbai Center", "Delhi North", "Bangalore Tech"], index=0) |
|
|
|
with col2: |
|
material_group = st.selectbox("Material Category", ["All Categories", "Critical Components", "Standard Parts"], index=0) |
|
|
|
with col3: |
|
time_period = st.selectbox("Time Period", ["Current Quarter", "FY2025", "Last 6 Months"], index=0) |
|
|
|
with col4: |
|
supplier_tier = st.selectbox("Supplier Tier", ["All Tiers", "Tier 1 Strategic", "Tier 2 Operational"], index=0) |
|
|
|
with col5: |
|
risk_level = st.selectbox("Risk Level", ["All Levels", "High Risk Only", "Medium Risk", "Low Risk"], index=0) |
|
|
|
with col6: |
|
performance = st.selectbox("Performance", ["All Performance", "Above Target", "Below Target"], index=0) |
|
|
|
with col7: |
|
geography = st.selectbox("Geography", ["Global View", "Asia Pacific", "Americas", "Europe"], index=0) |
|
|
|
with col8: |
|
update_freq = st.selectbox("Update Frequency", ["Real-time", "Hourly", "Daily"], index=0) |
|
|
|
|
|
material_df = get_material_data() |
|
metrics = get_enhanced_metrics() |
|
|
|
|
|
st.markdown('<div class="section-header">Key Performance Indicators</div>', unsafe_allow_html=True) |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
st.markdown(f""" |
|
<div class="metric-card"> |
|
<div class="metric-number">{metrics['fulfillment']}%</div> |
|
<div class="metric-label">Overall Fulfillment</div> |
|
<div class="metric-change metric-positive">β +{metrics['mom_change']}% MoM</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
with col2: |
|
st.markdown(f""" |
|
<div class="metric-card"> |
|
<div class="metric-number">{metrics['on_time_delivery']}%</div> |
|
<div class="metric-label">On-Time Delivery</div> |
|
<div class="metric-change metric-positive">β +1.2% WoW</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
with col3: |
|
st.markdown(f""" |
|
<div class="metric-card"> |
|
<div class="metric-number">{metrics['material_groups_at_risk']}%</div> |
|
<div class="metric-label">At-Risk Categories</div> |
|
<div class="metric-change metric-positive">β {metrics['risk_mom_change']}% MoM</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
with col4: |
|
st.markdown(f""" |
|
<div class="metric-card"> |
|
<div class="metric-number">{metrics['active_suppliers']:,}</div> |
|
<div class="metric-label">Active Suppliers</div> |
|
<div class="metric-change metric-neutral">+15 new</div> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown('<div class="section-header">Material Group Performance</div>', unsafe_allow_html=True) |
|
|
|
|
|
st.dataframe(material_df, use_container_width=True, hide_index=True) |
|
|
|
|
|
st.markdown('<div class="section-header">Performance Analytics</div>', unsafe_allow_html=True) |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
fig1 = px.bar( |
|
material_df, |
|
x='Material Group', |
|
y=['Current Rate', 'Target Rate'], |
|
title="Fulfillment Rates: Current vs Target", |
|
color_discrete_sequence=['#3b82f6', '#64748b'] |
|
) |
|
|
|
fig1.update_layout( |
|
plot_bgcolor='white', |
|
paper_bgcolor='white', |
|
font_family="Inter", |
|
title_font_size=14, |
|
title_font_color="#1e293b", |
|
xaxis_tickangle=-45, |
|
height=400, |
|
showlegend=True, |
|
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) |
|
) |
|
|
|
st.plotly_chart(fig1, use_container_width=True) |
|
|
|
with col2: |
|
|
|
risk_counts = material_df['Risk Level'].value_counts() |
|
|
|
fig2 = px.pie( |
|
values=risk_counts.values, |
|
names=risk_counts.index, |
|
title="Risk Distribution", |
|
color_discrete_sequence=['#22c55e', '#f59e0b', '#ef4444'] |
|
) |
|
|
|
fig2.update_layout( |
|
plot_bgcolor='white', |
|
paper_bgcolor='white', |
|
font_family="Inter", |
|
title_font_size=14, |
|
title_font_color="#1e293b", |
|
height=400 |
|
) |
|
|
|
st.plotly_chart(fig2, use_container_width=True) |
|
|
|
|
|
st.markdown(""" |
|
<div style="text-align: center; padding: 1.5rem; color: #64748b; border-top: 1px solid #e2e8f0; margin-top: 2rem; font-size: 0.875rem;"> |
|
Dashboard last updated: {timestamp} β’ Auto-refresh: Every 15 minutes β’ Data accuracy: 99.7% |
|
</div> |
|
""".format(timestamp=datetime.now().strftime("%B %d, %Y at %I:%M %p")), unsafe_allow_html=True) |
|
|