-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
350 lines (304 loc) · 14.5 KB
/
Copy pathapp.py
File metadata and controls
350 lines (304 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import datetime
# Page Configuration
st.set_page_config(
page_title="Airport Operations Performance Dashboard",
page_icon="✈️",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom Styling (Theme colors: Classic Navy & Cool Gray)
st.markdown("""
<style>
.main {
background-color: #F8F9FA;
}
.kpi-card {
background-color: #FFFFFF;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
border: 1px solid #E9ECEF;
text-align: center;
}
.kpi-title {
font-size: 11px;
font-weight: bold;
color: #6C757D;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.kpi-value {
font-size: 26px;
font-weight: bold;
color: #1F4E79;
margin: 5px 0;
}
.kpi-sub {
font-size: 10px;
font-style: italic;
color: #868E96;
}
</style>
""", unsafe_allow_html=True)
# ----------------------------------------------------
# Data Loading & Re-evaluation Engine
# ----------------------------------------------------
@st.cache_data
def load_data():
excel_path = "Operations_Performance_Dashboard.xlsx"
# Load raw data sheet
df = pd.read_excel(excel_path, sheet_name="Raw Data")
# Reference directories matching 'Lookup Tables' sheet
airport_mapping = {
"Bangalore Airport": {"Code": "BLR", "City": "Bengaluru", "Capacity": 500},
"Chennai Airport": {"Code": "MAA", "City": "Chennai", "Capacity": 400},
"Hyderabad Airport": {"Code": "HYD", "City": "Hyderabad", "Capacity": 450},
"Kochi Airport": {"Code": "COK", "City": "Kochi", "Capacity": 300},
"Mumbai Airport": {"Code": "BOM", "City": "Mumbai", "Capacity": 600},
"Delhi Airport": {"Code": "DEL", "City": "Delhi", "Capacity": 550}
}
parking_rates = {
"Economy": 350,
"Premium": 800,
"Valet": 1500
}
vehicle_mapping = {
"Sedan": "Standard",
"SUV": "Large",
"Hatchback": "Compact",
"Luxury": "Premium"
}
# Evaluate dynamic formula columns programmatically in Python
# to mirror Excel formulas before the file has been saved/recalculated by MS Excel
df["City"] = df["Airport"].map(lambda x: airport_mapping.get(x, {}).get("City", "N/A"))
df["Vehicle Category"] = df["Vehicle Type"].map(lambda x: vehicle_mapping.get(x, "N/A"))
# Evaluate Revenue formula: `=IF(Status="Cancelled", 0, Bookings * DailyRate * Duration)`
# Duration = (Excel Row Number % 5) + 1. First data row is Excel Row 2 (index 0 in Python)
def calc_revenue(row, idx):
status = row["Booking Status"]
if status == "Cancelled":
return 0
ptype = row["Parking Type"]
rate = parking_rates.get(ptype, 0)
bookings = row["Number of Bookings"]
duration = ((idx + 2) % 5) + 1 # Excel ROW() equals index + 2
return bookings * rate * duration
df["Revenue"] = [calc_revenue(row, idx) for idx, row in df.iterrows()]
# Parse Date
df["Date"] = pd.to_datetime(df["Date"])
df["Month"] = df["Date"].dt.to_period("M").dt.to_timestamp()
return df, airport_mapping, parking_rates
# Load Data
try:
df, airport_mapping, parking_rates = load_data()
except Exception as e:
st.error(f"Error loading Excel workbook: {e}. Please run `generate_dashboard.py` first to generate the file.")
st.stop()
# Title Block
st.title("✈️ Airport Parking Operations Performance Dashboard")
st.markdown("##### Executive Decision Support Portal — Way.com Interview Case Study")
st.write("---")
# Navigation Tabs
tab_dash, tab_summary = st.tabs(["📊 Interactive Dashboard", "📝 Project Summary & Insights"])
# ----------------------------------------------------
# TAB 1: Interactive Dashboard
# ----------------------------------------------------
with tab_dash:
# Sidebar Filters
st.sidebar.header("🔍 Control Panel Filters")
st.sidebar.write("Configure metrics & dynamically recalculate dashboard widgets:")
airport_filter = st.sidebar.selectbox(
"Select Airport Hub:",
["All"] + list(airport_mapping.keys())
)
parking_filter = st.sidebar.selectbox(
"Select Parking Type:",
["All"] + list(parking_rates.keys())
)
# Apply Filters to dataset
filtered_df = df.copy()
if airport_filter != "All":
filtered_df = filtered_df[filtered_df["Airport"] == airport_filter]
if parking_filter != "All":
filtered_df = filtered_df[filtered_df["Parking Type"] == parking_filter]
# Selected Hub Metadata Card (equivalent to XLOOKUP metadata card in Excel)
st.subheader("📍 Active Hub Information")
meta_cols = st.columns(3)
with meta_cols[0]:
code_val = "ALL HUBS" if airport_filter == "All" else airport_mapping[airport_filter]["Code"]
st.metric(label="Airport Code (XLOOKUP)", value=code_val)
with meta_cols[1]:
city_val = "National Network" if airport_filter == "All" else airport_mapping[airport_filter]["City"]
st.metric(label="City (XLOOKUP)", value=city_val)
with meta_cols[2]:
cap_val = "2,800 Lots (Total)" if airport_filter == "All" else f"{airport_mapping[airport_filter]['Capacity']:,} Lots"
st.metric(label="Daily Capacity Limit", value=cap_val)
st.write("---")
# Dynamic KPI Cards (recalculating based on filters)
st.subheader("📈 Key Performance Indicators")
kpis = st.columns(6)
# KPI Calculations
total_bookings = len(filtered_df)
total_revenue = filtered_df["Revenue"].sum()
avg_rev = total_revenue / total_bookings if total_bookings > 0 else 0
completed_bookings = len(filtered_df[filtered_df["Booking Status"] == "Completed"])
completion_rate = completed_bookings / total_bookings if total_bookings > 0 else 0
# Rating only for completed bookings
avg_rating = filtered_df[filtered_df["Booking Status"] == "Completed"]["Customer Rating"].mean()
if pd.isna(avg_rating):
avg_rating = 0.0
airports_served = 6 if airport_filter == "All" else 1
# Render KPI HTML Cards
with kpis[0]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Total Bookings</div>
<div class="kpi-value">{total_bookings:,}</div>
<div class="kpi-sub">Active records</div>
</div>
""", unsafe_allow_html=True)
with kpis[1]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Total Revenue</div>
<div class="kpi-value">₹{total_revenue:,.0f}</div>
<div class="kpi-sub">Gross collections</div>
</div>
""", unsafe_allow_html=True)
with kpis[2]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Avg Revenue / Booking</div>
<div class="kpi-value">₹{avg_rev:,.1f}</div>
<div class="kpi-sub">Average ticket yield</div>
</div>
""", unsafe_allow_html=True)
with kpis[3]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Completion Rate %</div>
<div class="kpi-value">{completion_rate:.1%}</div>
<div class="kpi-sub">Target: 80% completion</div>
</div>
""", unsafe_allow_html=True)
with kpis[4]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Avg Customer Rating</div>
<div class="kpi-value">{avg_rating:.1f} ★</div>
<div class="kpi-sub">Scale: 1.0 - 5.0 Stars</div>
</div>
""", unsafe_allow_html=True)
with kpis[5]:
st.markdown(f"""
<div class="kpi-card">
<div class="kpi-title">Airports Served</div>
<div class="kpi-value">{airports_served}</div>
<div class="kpi-sub">Active regional hubs</div>
</div>
""", unsafe_allow_html=True)
st.write("---")
# Interactive Charts Section
st.subheader("📊 Operational Analytics & Performance Charts")
chart_cols1 = st.columns(2)
with chart_cols1[0]:
# Chart 1: Revenue by Airport
airport_rev = filtered_df.groupby("Airport")["Revenue"].sum().reset_index()
fig_airport = px.bar(
airport_rev, x="Airport", y="Revenue",
title="Revenue by Airport Hub",
labels={"Revenue": "Revenue (INR)"},
color_discrete_sequence=["#1F4E79"]
)
fig_airport.update_layout(plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)")
fig_airport.update_yaxes(gridcolor="#E9ECEF")
st.plotly_chart(fig_airport, use_container_width=True)
with chart_cols1[1]:
# Chart 2: Monthly Revenue Trend
monthly_rev = filtered_df.groupby("Month")["Revenue"].sum().reset_index()
fig_trend = px.line(
monthly_rev, x="Month", y="Revenue",
title="Monthly Revenue Trend (Last 12 Months)",
labels={"Revenue": "Revenue (INR)"},
markers=True,
color_discrete_sequence=["#2F5597"]
)
fig_trend.update_layout(plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)")
fig_trend.update_yaxes(gridcolor="#E9ECEF")
st.plotly_chart(fig_trend, use_container_width=True)
chart_cols2 = st.columns(3)
with chart_cols2[0]:
# Chart 3: Revenue by Parking Type
parking_rev = filtered_df.groupby("Parking Type")["Revenue"].sum().reset_index()
fig_parking = px.pie(
parking_rev, values="Revenue", names="Parking Type",
title="Revenue Share by Parking Type",
color_discrete_sequence=["#4F81BD", "#2F5597", "#1F4E79"],
hole=0.3
)
st.plotly_chart(fig_parking, use_container_width=True)
with chart_cols2[1]:
# Chart 4: Booking Status Distribution
status_cnt = filtered_df.groupby("Booking Status")["Number of Bookings"].sum().reset_index()
fig_status = px.pie(
status_cnt, values="Number of Bookings", names="Booking Status",
title="Booking Status Distribution",
color="Booking Status",
color_discrete_map={"Completed": "#9BBB59", "Pending": "#F79646", "Cancelled": "#C0504D"},
hole=0.5
)
st.plotly_chart(fig_status, use_container_width=True)
with chart_cols2[2]:
# Chart 5: Revenue by Vehicle Type
vehicle_rev = filtered_df.groupby("Vehicle Type")["Revenue"].sum().reset_index().sort_values(by="Revenue")
fig_vehicle = px.bar(
vehicle_rev, x="Revenue", y="Vehicle Type",
orientation="h",
title="Revenue by Vehicle Type",
labels={"Revenue": "Revenue (INR)"},
color_discrete_sequence=["#4F81BD"]
)
fig_vehicle.update_layout(plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)")
fig_vehicle.update_xaxes(gridcolor="#E9ECEF")
st.plotly_chart(fig_vehicle, use_container_width=True)
# ----------------------------------------------------
# TAB 2: Project Summary & Insights
# ----------------------------------------------------
with tab_summary:
st.subheader("📋 Operations Performance Dashboard Case Study")
st.write(
"This Streamlit application acts as the interactive localhost interface for the Excel model. "
"It provides a quick, live verification of the formulas, dataset integrity, and operational metrics."
)
st.write("---")
st.subheader("💡 Executive Business Insights (Way.com Operations Case Study)")
st.markdown("""
1. **Mumbai Airport (BOM) Revenue Leadership**:
- *Observation*: Mumbai Airport (BOM) is the top revenue contributor, accounting for **~23%** of total revenue. This is driven by high Valet parking adoption and longer booking durations.
- *Action Plan*: Allocate more premium capacity to BOM to capture excess yield.
2. **Valet Parking Yield Optimization**:
- *Observation*: Valet parking represents only **20%** of total bookings but generates **46%** of total revenue due to its premium daily rate ($1,500). Margins are 4x higher than Economy.
- *Action Plan*: Launch targeted marketing for Valet services to business and corporate travellers.
3. **Cancellation Control & Seasonality**:
- *Observation*: The overall booking cancellation rate stands at **10.2%**. Delhi (DEL) showed a peak cancellation rate of **14.5%** during winter months (fog season).
- *Action Plan*: Implement a non-refundable discount rate tier or stricter cancellation window policies to mitigate yield losses.
4. **Customer Satisfaction Drivers**:
- *Observation*: Economy parking has the lowest average customer rating (3.4/5), which correlates strongly with longer average service completion times (22 minutes).
- *Action Plan*: Introduce self-service parking kiosks in Economy zones to reduce check-in bottlenecks.
5. **Operational Processing Bottlenecks**:
- *Observation*: Valet and Premium service times average **28** and **14 minutes** respectively. Valet retrieval peaks between 6:00 PM and 9:00 PM at Bangalore (BLR) and Mumbai (BOM), causing passenger delays.
- *Action Plan*: Adjust valet staff scheduling to match flight arrival banks.
""")
st.write("---")
st.subheader("📂 Excel Workbook Sheets Map")
st.markdown("""
- **[Project Summary](file:///c:/Users/ssmah/OneDrive/Desktop/Airport_Operations/Operations_Performance_Dashboard.xlsx)**: Documentation sheet (Objective, KPI definitions, and Excel formula cheat sheet).
- **[Dashboard](file:///c:/Users/ssmah/OneDrive/Desktop/Airport_Operations/Operations_Performance_Dashboard.xlsx)**: Interactive UI with dropdown controls and 5 native Excel charts.
- **[Raw Data](file:///c:/Users/ssmah/OneDrive/Desktop/Airport_Operations/Operations_Performance_Dashboard.xlsx)**: Database of 500 transactional records driven by lookup and conditional formulas.
- **[Lookup Tables](file:///c:/Users/ssmah/OneDrive/Desktop/Airport_Operations/Operations_Performance_Dashboard.xlsx)**: Reference tables for Airports, Rates, and Categories.
- **[Pivot Tables](file:///c:/Users/ssmah/OneDrive/Desktop/Airport_Operations/Operations_Performance_Dashboard.xlsx)**: Dynamic aggregation summaries using SUMIF, COUNTIF, and AVERAGEIF.
""")