charlottegers commited on
Commit
593a0ec
·
verified ·
1 Parent(s): 954654c

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +17 -6
  2. app.py +390 -0
  3. requirements.txt +5 -0
  4. synthetic_delivery_data.csv +0 -0
README.md CHANGED
@@ -1,12 +1,23 @@
1
  ---
2
- title: Deliveryoptimizationapp
3
- emoji: 📚
4
- colorFrom: red
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.13.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Delivery Delay Risk Intelligence Dashboard
3
+ emoji: 🚚
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 5.0.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Delivery Delay Risk Intelligence Dashboard
13
+
14
+ This Hugging Face Space supports a university group project on delivery delay risk.
15
+
16
+ It combines:
17
+ - real-world/found delivery logistics data uploaded as CSV,
18
+ - synthetic delivery-time generation logic,
19
+ - quantitative dashboard analysis,
20
+ - qualitative business recommendations,
21
+ - automation through an AI-enhanced interface.
22
+
23
+ Users can upload a delivery dataset or use the included sample dataset, filter operational conditions, identify high-risk delay factors, simulate a new delivery, and download an executive summary.
app.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ from sklearn.ensemble import RandomForestClassifier
6
+ from sklearn.preprocessing import OneHotEncoder
7
+ from sklearn.compose import ColumnTransformer
8
+ from sklearn.pipeline import Pipeline
9
+ from pathlib import Path
10
+ import tempfile
11
+
12
+ DATA_PATH = Path("synthetic_delivery_data.csv")
13
+
14
+ NUMERIC_COLS = [
15
+ "distance_km", "package_weight_kg", "delivery_time_hours",
16
+ "expected_time_hours", "delivery_rating", "delivery_cost"
17
+ ]
18
+ CAT_COLS = [
19
+ "delivery_partner", "package_type", "vehicle_type", "delivery_mode",
20
+ "region", "weather_condition", "delayed", "delivery_status"
21
+ ]
22
+
23
+ CUSTOM_CSS = """
24
+ .gradio-container {max-width: 1280px !important; margin: auto;}
25
+ .metric-card {background: linear-gradient(135deg, #ffffff, #f7f8fb); border: 1px solid #e8e8ef; border-radius: 18px; padding: 18px; box-shadow: 0 8px 24px rgba(0,0,0,.05);}
26
+ .metric-label {font-size: 13px; color: #5f6470; margin-bottom: 6px;}
27
+ .metric-value {font-size: 30px; font-weight: 800; color: #111827;}
28
+ .insight-box {background: #111827; color: white; border-radius: 18px; padding: 20px; line-height: 1.55;}
29
+ .small-muted {color: #6b7280; font-size: 13px;}
30
+ """
31
+
32
+
33
+ def _clean_time_column(series):
34
+ """Convert either normal numbers or timestamp-looking duration strings into numeric hours."""
35
+ if pd.api.types.is_numeric_dtype(series):
36
+ return pd.to_numeric(series, errors="coerce")
37
+ s = series.astype(str)
38
+ # Handles values like 1970-01-01 00:00:00.000000008 by extracting last part.
39
+ extracted = s.str.split(".").str[-1]
40
+ return pd.to_numeric(extracted, errors="coerce")
41
+
42
+
43
+ def load_and_prepare(file_obj=None):
44
+ if file_obj is None:
45
+ df = pd.read_csv(DATA_PATH)
46
+ else:
47
+ df = pd.read_csv(file_obj.name)
48
+
49
+ df = df.copy()
50
+ df.columns = df.columns.str.strip().str.lower()
51
+ df = df.drop_duplicates()
52
+
53
+ required_minimum = ["distance_km", "vehicle_type", "weather_condition", "delivery_mode", "region"]
54
+ missing_required = [c for c in required_minimum if c not in df.columns]
55
+ if missing_required:
56
+ raise gr.Error(f"Your file is missing these required columns: {missing_required}")
57
+
58
+ for col in ["delivery_time_hours", "expected_time_hours"]:
59
+ if col in df.columns:
60
+ df[col] = _clean_time_column(df[col])
61
+
62
+ for col in NUMERIC_COLS:
63
+ if col in df.columns:
64
+ df[col] = pd.to_numeric(df[col], errors="coerce")
65
+ df[col] = df[col].fillna(df[col].median())
66
+
67
+ for col in CAT_COLS:
68
+ if col in df.columns:
69
+ df[col] = df[col].astype(str).str.strip().str.lower()
70
+ if df[col].isna().any():
71
+ df[col] = df[col].fillna(df[col].mode()[0])
72
+
73
+ # If expected/delivery time are not reliable or missing, rebuild them with business logic.
74
+ df = create_synthetic_time_logic(df)
75
+ df["delay_hours"] = (df["delivery_time_hours"] - df["expected_time_hours"]).round(2)
76
+ df["calculated_delay"] = np.where(df["delay_hours"] > 0, "yes", "no")
77
+ df["delay_score"] = df["delay_hours"].apply(delay_score)
78
+ df["performance_label"] = df["delay_score"].apply(performance_label)
79
+ df["distance_category"] = pd.cut(
80
+ df["distance_km"],
81
+ bins=[0, 50, 150, 300, float("inf")],
82
+ labels=["short", "medium", "long", "very long"],
83
+ include_lowest=True,
84
+ ).astype(str)
85
+ return df
86
+
87
+
88
+ def create_synthetic_time_logic(df):
89
+ df = df.copy()
90
+ for col in ["vehicle_type", "weather_condition", "delivery_mode", "region"]:
91
+ df[col] = df[col].astype(str).str.strip().str.lower()
92
+
93
+ vehicle_adjustment = {"bike": 1.2, "van": 0.5, "truck": 0.8, "ev van": 0.4}
94
+ weather_adjustment = {"clear": 0.0, "cloudy": 0.2, "foggy": 0.6, "rainy": 0.8, "stormy": 1.2, "cold": 0.2, "hot": 0.2, "windy": 0.3}
95
+ mode_adjustment = {"same day": 0.3, "express": 0.2, "two day": 0.7, "standard": 0.5}
96
+ region_adjustment = {"central": 0.6, "north": 0.3, "south": 0.3, "east": 0.4, "west": 0.4}
97
+
98
+ expected = (
99
+ df["distance_km"] / 45
100
+ + df["vehicle_type"].map(vehicle_adjustment).fillna(0.5)
101
+ + df["weather_condition"].map(weather_adjustment).fillna(0.3)
102
+ + df["delivery_mode"].map(mode_adjustment).fillna(0.4)
103
+ + df["region"].map(region_adjustment).fillna(0.3)
104
+ ).clip(lower=0.5)
105
+
106
+ vehicle_mult = {"bike": 1.05, "van": 0.95, "truck": 1.02, "ev van": 0.97}
107
+ weather_mult = {"clear": 0.95, "cloudy": 1.00, "foggy": 1.05, "rainy": 1.10, "stormy": 1.20, "cold": 1.02, "hot": 1.02, "windy": 1.03}
108
+ mode_mult = {"same day": 1.05, "express": 1.02, "two day": 0.97, "standard": 1.00}
109
+ region_mult = {"central": 1.08, "north": 1.00, "south": 1.01, "east": 1.02, "west": 1.03}
110
+
111
+ actual = (
112
+ expected
113
+ * df["vehicle_type"].map(vehicle_mult).fillna(1)
114
+ * df["weather_condition"].map(weather_mult).fillna(1)
115
+ * df["delivery_mode"].map(mode_mult).fillna(1)
116
+ * df["region"].map(region_mult).fillna(1)
117
+ ).clip(lower=0.5)
118
+
119
+ ratio = actual / expected
120
+ balanced_actual = np.where(
121
+ ratio < 0.98, expected * 0.95,
122
+ np.where(ratio < 1.05, expected * 1.00,
123
+ np.where(ratio < 1.15, expected * 1.10, expected * 1.25))
124
+ )
125
+ df["expected_time_hours"] = expected.round(2)
126
+ df["delivery_time_hours"] = pd.Series(balanced_actual).round(2)
127
+ return df
128
+
129
+
130
+ def delay_score(delay):
131
+ if delay <= 0: return 5
132
+ if delay <= 2: return 4
133
+ if delay <= 5: return 3
134
+ if delay <= 8: return 2
135
+ return 1
136
+
137
+
138
+ def performance_label(score):
139
+ return {5: "excellent", 4: "good", 3: "average", 2: "poor", 1: "critical"}.get(int(score), "unknown")
140
+
141
+
142
+ def filter_df(df, vehicle, weather, mode, region):
143
+ out = df.copy()
144
+ filters = {"vehicle_type": vehicle, "weather_condition": weather, "delivery_mode": mode, "region": region}
145
+ for col, selected in filters.items():
146
+ if selected and "all" not in selected:
147
+ out = out[out[col].isin(selected)]
148
+ return out
149
+
150
+
151
+ def kpi_html(df):
152
+ total = len(df)
153
+ delay_rate = (df["calculated_delay"].eq("yes").mean() * 100) if total else 0
154
+ avg_delay = df["delay_hours"].mean() if total else 0
155
+ avg_score = df["delay_score"].mean() if total else 0
156
+ cost = df["delivery_cost"].mean() if "delivery_cost" in df.columns and total else 0
157
+ return f"""
158
+ <div style='display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;'>
159
+ <div class='metric-card'><div class='metric-label'>Deliveries analyzed</div><div class='metric-value'>{total:,.0f}</div></div>
160
+ <div class='metric-card'><div class='metric-label'>Delay rate</div><div class='metric-value'>{delay_rate:.1f}%</div></div>
161
+ <div class='metric-card'><div class='metric-label'>Average delay hours</div><div class='metric-value'>{avg_delay:.2f}</div></div>
162
+ <div class='metric-card'><div class='metric-label'>Avg. delay score</div><div class='metric-value'>{avg_score:.2f}/5</div></div>
163
+ </div>
164
+ <p class='small-muted'>Average delivery cost in filtered data: {cost:,.2f}</p>
165
+ """
166
+
167
+
168
+ def group_summary(df, col):
169
+ return (
170
+ df.groupby(col, observed=False)
171
+ .agg(
172
+ deliveries=(col, "size"),
173
+ delay_rate=("calculated_delay", lambda x: round((x.eq("yes").mean() * 100), 2)),
174
+ avg_delay_hours=("delay_hours", "mean"),
175
+ avg_delay_score=("delay_score", "mean"),
176
+ avg_distance_km=("distance_km", "mean"),
177
+ )
178
+ .round(2)
179
+ .sort_values(["delay_rate", "avg_delay_hours"], ascending=False)
180
+ .reset_index()
181
+ )
182
+
183
+
184
+ def make_charts(df):
185
+ by_vehicle = group_summary(df, "vehicle_type")
186
+ by_weather = group_summary(df, "weather_condition")
187
+ by_region = group_summary(df, "region")
188
+ by_mode = group_summary(df, "delivery_mode")
189
+
190
+ fig_vehicle = px.bar(by_vehicle, x="vehicle_type", y="delay_rate", text="delay_rate", title="Delay Risk by Vehicle Type")
191
+ fig_weather = px.bar(by_weather, x="weather_condition", y="avg_delay_hours", text="avg_delay_hours", title="Average Delay Hours by Weather")
192
+ fig_region = px.bar(by_region, x="region", y="delay_rate", text="delay_rate", title="Delay Rate by Region")
193
+ fig_mode = px.bar(by_mode, x="delivery_mode", y="avg_delay_score", text="avg_delay_score", title="Performance Score by Delivery Mode")
194
+ fig_scatter = px.scatter(df.sample(min(len(df), 2000), random_state=42), x="distance_km", y="delay_hours", color="weather_condition", hover_data=["vehicle_type", "delivery_mode", "region"], title="Distance vs Delay Hours")
195
+
196
+ for fig in [fig_vehicle, fig_weather, fig_region, fig_mode, fig_scatter]:
197
+ fig.update_layout(template="plotly_white", height=430, margin=dict(l=40, r=20, t=60, b=40))
198
+ return fig_vehicle, fig_weather, fig_region, fig_mode, fig_scatter
199
+
200
+
201
+ def train_feature_importance(df):
202
+ model_cols = ["vehicle_type", "weather_condition", "delivery_mode", "region", "distance_category", "distance_km", "package_weight_kg"]
203
+ model_cols = [c for c in model_cols if c in df.columns]
204
+ X = df[model_cols]
205
+ y = df["calculated_delay"].eq("yes").astype(int)
206
+ cat = [c for c in model_cols if X[c].dtype == "object" or str(X[c].dtype) == "category"]
207
+ num = [c for c in model_cols if c not in cat]
208
+ pre = ColumnTransformer([("cat", OneHotEncoder(handle_unknown="ignore"), cat), ("num", "passthrough", num)])
209
+ clf = RandomForestClassifier(n_estimators=80, random_state=42, max_depth=7)
210
+ pipe = Pipeline([("pre", pre), ("clf", clf)])
211
+ pipe.fit(X, y)
212
+ names = list(pipe.named_steps["pre"].get_feature_names_out())
213
+ importances = pipe.named_steps["clf"].feature_importances_
214
+ imp = pd.DataFrame({"factor": names, "importance": importances}).sort_values("importance", ascending=False).head(12)
215
+ imp["factor"] = imp["factor"].str.replace("cat__", "", regex=False).str.replace("num__", "", regex=False)
216
+ fig = px.bar(imp.sort_values("importance"), x="importance", y="factor", orientation="h", title="AI Model: Most Important Delay-Risk Drivers")
217
+ fig.update_layout(template="plotly_white", height=470, margin=dict(l=120, r=20, t=60, b=40))
218
+ return fig, imp
219
+
220
+
221
+ def auto_insights(df):
222
+ if len(df) == 0:
223
+ return "<div class='insight-box'>No data available for the selected filters.</div>"
224
+ summaries = {c: group_summary(df, c) for c in ["vehicle_type", "weather_condition", "delivery_mode", "region", "distance_category"] if c in df.columns}
225
+ worst = {k: v.iloc[0] for k, v in summaries.items() if len(v) > 0}
226
+ best = {k: v.sort_values(["delay_rate", "avg_delay_hours"], ascending=True).iloc[0] for k, v in summaries.items() if len(v) > 0}
227
+ top_risk_text = "<br>".join([f"• <b>{k.replace('_',' ').title()}</b>: highest risk = <b>{row[k]}</b> ({row['delay_rate']:.1f}% delay rate, {row['avg_delay_hours']:.2f} avg delay hours)" for k, row in worst.items()])
228
+ best_text = "<br>".join([f"• <b>{k.replace('_',' ').title()}</b>: best performer = <b>{row[k]}</b> ({row['delay_rate']:.1f}% delay rate)" for k, row in best.items()])
229
+ delay_rate = df["calculated_delay"].eq("yes").mean() * 100
230
+ recommendation = "Prioritize operational buffers for the highest-risk combinations, especially where bad weather, central routes, same-day delivery, or slower vehicle types overlap."
231
+ if delay_rate > 35:
232
+ recommendation += " The current filtered scenario has a high delay rate, so management should add contingency capacity and proactively communicate expected delays to customers."
233
+ else:
234
+ recommendation += " The current filtered scenario is relatively manageable, so management can focus on monitoring and selective process improvements."
235
+ return f"""
236
+ <div class='insight-box'>
237
+ <h3>AI-enhanced executive interpretation</h3>
238
+ <p><b>Business challenge:</b> Which operational factors create the highest delivery-delay risk, and what should management do?</p>
239
+ <p><b>Highest-risk factors found in the filtered data:</b><br>{top_risk_text}</p>
240
+ <p><b>Best-performing conditions:</b><br>{best_text}</p>
241
+ <p><b>Management action:</b> {recommendation}</p>
242
+ <p><b>Qualitative interpretation:</b> Delay risk is not only a numeric issue. It affects customer trust, service reliability, driver planning, and cost control. The dashboard therefore combines quantitative KPIs with qualitative business recommendations.</p>
243
+ </div>
244
+ """
245
+
246
+
247
+ def update_dashboard(file_obj, vehicle, weather, mode, region):
248
+ df = load_and_prepare(file_obj)
249
+ fdf = filter_df(df, vehicle, weather, mode, region)
250
+ if len(fdf) == 0:
251
+ raise gr.Error("Your filters produced no rows. Select fewer filters.")
252
+ figs = make_charts(fdf)
253
+ model_fig, imp = train_feature_importance(fdf)
254
+ sample = fdf.head(15)
255
+ tables = [group_summary(fdf, c) for c in ["vehicle_type", "weather_condition", "region", "delivery_mode", "distance_category"]]
256
+ return (kpi_html(fdf), auto_insights(fdf), *figs, model_fig, *tables, sample)
257
+
258
+
259
+ def choices_from_data(file_obj=None):
260
+ df = load_and_prepare(file_obj)
261
+ return [
262
+ gr.update(choices=sorted(df["vehicle_type"].dropna().unique().tolist()), value=[]),
263
+ gr.update(choices=sorted(df["weather_condition"].dropna().unique().tolist()), value=[]),
264
+ gr.update(choices=sorted(df["delivery_mode"].dropna().unique().tolist()), value=[]),
265
+ gr.update(choices=sorted(df["region"].dropna().unique().tolist()), value=[]),
266
+ ]
267
+
268
+
269
+ def simulate_delivery(distance, weight, vehicle, weather, mode, region):
270
+ row = pd.DataFrame({
271
+ "distance_km": [distance], "package_weight_kg": [weight], "vehicle_type": [vehicle],
272
+ "weather_condition": [weather], "delivery_mode": [mode], "region": [region]
273
+ })
274
+ row = create_synthetic_time_logic(row)
275
+ row["delay_hours"] = (row["delivery_time_hours"] - row["expected_time_hours"]).round(2)
276
+ row["delay_score"] = row["delay_hours"].apply(delay_score)
277
+ row["performance_label"] = row["delay_score"].apply(performance_label)
278
+ risk = "HIGH RISK" if row.loc[0, "delay_hours"] > 0 else "LOW RISK"
279
+ return f"""
280
+ ### Simulation Result
281
+ - Expected delivery time: **{row.loc[0, 'expected_time_hours']:.2f} hours**
282
+ - Predicted actual delivery time: **{row.loc[0, 'delivery_time_hours']:.2f} hours**
283
+ - Predicted delay: **{row.loc[0, 'delay_hours']:.2f} hours**
284
+ - Delay score: **{row.loc[0, 'delay_score']}/5**
285
+ - Performance label: **{row.loc[0, 'performance_label'].title()}**
286
+ - Risk classification: **{risk}**
287
+ """
288
+
289
+
290
+ def download_summary(file_obj, vehicle, weather, mode, region):
291
+ df = load_and_prepare(file_obj)
292
+ fdf = filter_df(df, vehicle, weather, mode, region)
293
+ summary = {
294
+ "rows_analyzed": len(fdf),
295
+ "delay_rate_percent": round(fdf["calculated_delay"].eq("yes").mean() * 100, 2),
296
+ "average_delay_hours": round(fdf["delay_hours"].mean(), 2),
297
+ "average_delay_score": round(fdf["delay_score"].mean(), 2),
298
+ }
299
+ lines = ["Delivery Delay Risk Executive Summary", "", "KPIs:"]
300
+ for k, v in summary.items():
301
+ lines.append(f"- {k.replace('_', ' ').title()}: {v}")
302
+ lines += ["", "Highest-risk groups:"]
303
+ for c in ["vehicle_type", "weather_condition", "delivery_mode", "region", "distance_category"]:
304
+ tab = group_summary(fdf, c)
305
+ row = tab.iloc[0]
306
+ lines.append(f"- {c}: {row[c]} | delay rate {row['delay_rate']}% | avg delay {row['avg_delay_hours']}h")
307
+ lines += ["", "Recommended actions:", "- Add operational buffers for high-risk weather and region combinations.", "- Match faster vehicle types to same-day and express deliveries.", "- Use the simulator before accepting risky delivery promises.", "- Monitor delay score weekly as an operational KPI."]
308
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="w", encoding="utf-8")
309
+ tmp.write("\n".join(lines))
310
+ tmp.close()
311
+ return tmp.name
312
+
313
+
314
+ base_df = load_and_prepare(None)
315
+ vehicle_choices = sorted(base_df["vehicle_type"].dropna().unique().tolist())
316
+ weather_choices = sorted(base_df["weather_condition"].dropna().unique().tolist())
317
+ mode_choices = sorted(base_df["delivery_mode"].dropna().unique().tolist())
318
+ region_choices = sorted(base_df["region"].dropna().unique().tolist())
319
+
320
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate"), css=CUSTOM_CSS, title="Delivery Delay Risk Dashboard") as demo:
321
+ gr.Markdown("""
322
+ # 🚚 Delivery Delay Risk Intelligence Dashboard
323
+ **AI-enhanced operations dashboard for identifying delivery delay risk factors and management actions.**
324
+
325
+ Upload your CSV or use the included dataset. The app cleans the data, generates realistic delivery-time logic, calculates delay risk, visualizes operational drivers, simulates new deliveries, and creates an executive summary.
326
+ """)
327
+
328
+ with gr.Row():
329
+ file_input = gr.File(label="Optional: upload your real-world/found delivery CSV", file_types=[".csv"])
330
+ refresh_btn = gr.Button("Load / refresh data", variant="primary")
331
+
332
+ with gr.Accordion("Filters", open=True):
333
+ with gr.Row():
334
+ vehicle_filter = gr.Dropdown(vehicle_choices, label="Vehicle type", multiselect=True)
335
+ weather_filter = gr.Dropdown(weather_choices, label="Weather condition", multiselect=True)
336
+ mode_filter = gr.Dropdown(mode_choices, label="Delivery mode", multiselect=True)
337
+ region_filter = gr.Dropdown(region_choices, label="Region", multiselect=True)
338
+
339
+ kpis = gr.HTML()
340
+ insights = gr.HTML()
341
+
342
+ with gr.Tab("Interactive dashboard"):
343
+ with gr.Row():
344
+ fig_vehicle = gr.Plot()
345
+ fig_weather = gr.Plot()
346
+ with gr.Row():
347
+ fig_region = gr.Plot()
348
+ fig_mode = gr.Plot()
349
+ fig_scatter = gr.Plot()
350
+
351
+ with gr.Tab("AI risk-driver model"):
352
+ model_fig = gr.Plot()
353
+ gr.Markdown("This section trains a simple Random Forest model inside the app to estimate which factors are most important for predicting delays.")
354
+
355
+ with gr.Tab("Summary tables"):
356
+ with gr.Row():
357
+ vehicle_table = gr.Dataframe(label="Vehicle performance")
358
+ weather_table = gr.Dataframe(label="Weather performance")
359
+ with gr.Row():
360
+ region_table = gr.Dataframe(label="Region performance")
361
+ mode_table = gr.Dataframe(label="Delivery mode performance")
362
+ distance_table = gr.Dataframe(label="Distance category performance")
363
+ sample_table = gr.Dataframe(label="Cleaned sample data")
364
+
365
+ with gr.Tab("Delivery risk simulator"):
366
+ with gr.Row():
367
+ sim_distance = gr.Slider(1, 500, value=120, label="Distance km")
368
+ sim_weight = gr.Slider(0.1, 60, value=10, label="Package weight kg")
369
+ with gr.Row():
370
+ sim_vehicle = gr.Dropdown(vehicle_choices, value=vehicle_choices[0], label="Vehicle")
371
+ sim_weather = gr.Dropdown(weather_choices, value=weather_choices[0], label="Weather")
372
+ sim_mode = gr.Dropdown(mode_choices, value=mode_choices[0], label="Mode")
373
+ sim_region = gr.Dropdown(region_choices, value=region_choices[0], label="Region")
374
+ sim_btn = gr.Button("Simulate delivery risk", variant="primary")
375
+ sim_output = gr.Markdown()
376
+
377
+ with gr.Tab("Download executive summary"):
378
+ gr.Markdown("Generate a short text summary for your presentation/report.")
379
+ download_btn = gr.Button("Create executive summary file")
380
+ download_file = gr.File(label="Download summary")
381
+
382
+ outputs = [kpis, insights, fig_vehicle, fig_weather, fig_region, fig_mode, fig_scatter, model_fig, vehicle_table, weather_table, region_table, mode_table, distance_table, sample_table]
383
+ refresh_btn.click(update_dashboard, inputs=[file_input, vehicle_filter, weather_filter, mode_filter, region_filter], outputs=outputs)
384
+ file_input.change(choices_from_data, inputs=[file_input], outputs=[vehicle_filter, weather_filter, mode_filter, region_filter])
385
+ sim_btn.click(simulate_delivery, inputs=[sim_distance, sim_weight, sim_vehicle, sim_weather, sim_mode, sim_region], outputs=sim_output)
386
+ download_btn.click(download_summary, inputs=[file_input, vehicle_filter, weather_filter, mode_filter, region_filter], outputs=download_file)
387
+ demo.load(update_dashboard, inputs=[file_input, vehicle_filter, weather_filter, mode_filter, region_filter], outputs=outputs)
388
+
389
+ if __name__ == "__main__":
390
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ pandas
3
+ numpy
4
+ plotly
5
+ scikit-learn
synthetic_delivery_data.csv ADDED
The diff for this file is too large to render. See raw diff