Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import random | |
| import warnings | |
| from datetime import datetime | |
| warnings.filterwarnings("ignore") | |
| random.seed(2025) | |
| np.random.seed(2025) | |
| APP_TITLE = "AI Delivery Performance Intelligence Dashboard" | |
| REQUIRED_COLUMNS = [ | |
| "delivery_id", "delivery_partner", "package_type", "vehicle_type", "delivery_mode", | |
| "region", "weather_condition", "distance_km", "package_weight_kg", | |
| "delivery_time_hours", "expected_time_hours", "delayed", | |
| "delivery_status", "delivery_rating", "delivery_cost" | |
| ] | |
| NUMERIC_COLS = [ | |
| "distance_km", "package_weight_kg", "delivery_time_hours", | |
| "expected_time_hours", "delivery_rating", "delivery_cost" | |
| ] | |
| CATEGORICAL_COLS = [ | |
| "delivery_partner", "package_type", "vehicle_type", "delivery_mode", | |
| "region", "weather_condition", "delayed", "delivery_status" | |
| ] | |
| CUSTOM_CSS = """ | |
| .gradio-container { | |
| max-width: 1500px !important; | |
| margin: auto !important; | |
| background: linear-gradient(135deg, #f8fafc 0%, #eef2ff 45%, #ffffff 100%); | |
| } | |
| #hero { | |
| padding: 34px 38px; | |
| border-radius: 28px; | |
| background: linear-gradient(135deg, #111827 0%, #1e293b 48%, #4f46e5 100%); | |
| color: white; | |
| box-shadow: 0 22px 55px rgba(15, 23, 42, 0.22); | |
| margin-bottom: 18px; | |
| } | |
| #hero h1 { | |
| font-size: 38px; | |
| line-height: 1.05; | |
| margin-bottom: 8px; | |
| color: white; | |
| } | |
| #hero p { | |
| font-size: 16px; | |
| opacity: 0.92; | |
| color: white; | |
| } | |
| .metric-card { | |
| padding: 24px; | |
| border-radius: 24px; | |
| background: rgba(255,255,255,0.90); | |
| border: 1px solid rgba(226,232,240,0.9); | |
| box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08); | |
| min-height: 150px; | |
| } | |
| .metric-label { | |
| font-size: 13px; | |
| color: #64748b; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| font-weight: 700; | |
| } | |
| .metric-value { | |
| font-size: 34px; | |
| color: #111827; | |
| font-weight: 850; | |
| margin-top: 8px; | |
| } | |
| .metric-note { | |
| font-size: 13px; | |
| color: #64748b; | |
| margin-top: 8px; | |
| } | |
| .insight-box { | |
| padding: 22px 24px; | |
| border-radius: 24px; | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| box-shadow: 0 12px 32px rgba(15, 23, 42, 0.08); | |
| } | |
| .warning-box { | |
| padding: 18px 22px; | |
| border-radius: 20px; | |
| background: #fff7ed; | |
| border: 1px solid #fed7aa; | |
| } | |
| .success-box { | |
| padding: 18px 22px; | |
| border-radius: 20px; | |
| background: #ecfdf5; | |
| border: 1px solid #bbf7d0; | |
| } | |
| .small-muted { | |
| color: #64748b; | |
| font-size: 13px; | |
| } | |
| """ | |
| def _safe_lower_text(df): | |
| for col in df.select_dtypes(include=["object"]).columns: | |
| df[col] = df[col].astype(str).str.strip() | |
| return df | |
| def _extract_time_number(series): | |
| s = series.astype(str).str.strip() | |
| # Handles strange strings like 1970-01-01 00:00:00.000000008 by extracting the final number. | |
| extracted = s.str.extract(r"(\d+\.?\d*)$")[0] | |
| numeric = pd.to_numeric(extracted, errors="coerce") | |
| # If a normal numeric string was provided, use it. | |
| fallback = pd.to_numeric(s, errors="coerce") | |
| return numeric.fillna(fallback) | |
| def validate_and_clean(file): | |
| if file is None: | |
| raise gr.Error("Please upload a CSV file first.") | |
| df = pd.read_csv(file.name) | |
| original_rows = len(df) | |
| df.columns = df.columns.str.strip().str.lower() | |
| missing_cols = [c for c in REQUIRED_COLUMNS if c not in df.columns] | |
| if missing_cols: | |
| raise gr.Error( | |
| "Your file is missing required columns: " | |
| + ", ".join(missing_cols) | |
| + ". Please upload Delivery_Logistics.csv or rename your columns." | |
| ) | |
| df = df.drop_duplicates() | |
| duplicate_rows = original_rows - len(df) | |
| df = _safe_lower_text(df) | |
| for col in ["delivery_time_hours", "expected_time_hours"]: | |
| df[col] = _extract_time_number(df[col]) | |
| for col in NUMERIC_COLS: | |
| df[col] = pd.to_numeric(df[col], errors="coerce") | |
| median_value = df[col].median() | |
| if pd.isna(median_value): | |
| median_value = 0 | |
| df[col] = df[col].fillna(median_value) | |
| for col in CATEGORICAL_COLS: | |
| df[col] = df[col].replace(["nan", "None", ""], np.nan) | |
| mode_value = df[col].mode(dropna=True) | |
| fill_value = mode_value.iloc[0] if len(mode_value) else "unknown" | |
| df[col] = df[col].fillna(fill_value).astype(str).str.strip().str.lower() | |
| cleaning_report = { | |
| "original_rows": original_rows, | |
| "final_rows": len(df), | |
| "duplicates_removed": duplicate_rows, | |
| "columns": len(df.columns), | |
| } | |
| return df, cleaning_report | |
| def enrich_delivery_logic(df, weather_sensitivity=1.0, traffic_pressure=1.0, capacity_pressure=1.0): | |
| out = df.copy() | |
| text_cols = ["vehicle_type", "weather_condition", "delivery_mode", "region", "package_type", "delivery_partner"] | |
| for col in text_cols: | |
| out[col] = out[col].astype(str).str.strip().str.lower() | |
| # Expected time model | |
| out["expected_time_hours"] = out["distance_km"] / 45 | |
| vehicle_adjustment = {"bike": 1.20, "van": 0.50, "truck": 0.80, "ev van": 0.40} | |
| weather_adjustment = { | |
| "clear": 0.00, "cloudy": 0.20, "foggy": 0.60, "rainy": 0.80, | |
| "stormy": 1.20, "cold": 0.20, "hot": 0.20, "windy": 0.30 | |
| } | |
| mode_adjustment = {"same day": 0.30, "express": 0.20, "two day": 0.70, "standard": 0.50} | |
| region_adjustment = {"central": 0.60, "north": 0.30, "south": 0.30, "east": 0.40, "west": 0.40} | |
| out["expected_time_hours"] = ( | |
| out["expected_time_hours"] | |
| + out["vehicle_type"].map(vehicle_adjustment).fillna(0.50) | |
| + out["weather_condition"].map(weather_adjustment).fillna(0.30) * weather_sensitivity | |
| + out["delivery_mode"].map(mode_adjustment).fillna(0.40) | |
| + out["region"].map(region_adjustment).fillna(0.30) * traffic_pressure | |
| ) | |
| # Actual time multipliers | |
| vehicle_actual_multiplier = {"bike": 1.05, "van": 0.95, "truck": 1.02, "ev van": 0.97} | |
| weather_actual_multiplier = { | |
| "clear": 0.95, "cloudy": 1.00, "foggy": 1.05, "rainy": 1.10, | |
| "stormy": 1.20, "cold": 1.02, "hot": 1.02, "windy": 1.03 | |
| } | |
| mode_actual_multiplier = {"same day": 1.05, "express": 1.02, "two day": 0.97, "standard": 1.00} | |
| region_actual_multiplier = {"central": 1.08, "north": 1.00, "south": 1.01, "east": 1.02, "west": 1.03} | |
| out["delivery_time_hours"] = ( | |
| out["expected_time_hours"] | |
| * out["vehicle_type"].map(vehicle_actual_multiplier).fillna(1.00) | |
| * (out["weather_condition"].map(weather_actual_multiplier).fillna(1.00) ** weather_sensitivity) | |
| * out["delivery_mode"].map(mode_actual_multiplier).fillna(1.00) | |
| * (out["region"].map(region_actual_multiplier).fillna(1.00) ** traffic_pressure) | |
| * capacity_pressure | |
| ) | |
| # Controlled variation to keep realistic early/on-time/late spread | |
| out["delay_ratio"] = out["delivery_time_hours"] / out["expected_time_hours"] | |
| out["delivery_time_hours"] = np.where( | |
| out["delay_ratio"] < 0.98, | |
| out["expected_time_hours"] * 0.95, | |
| np.where( | |
| out["delay_ratio"] < 1.05, | |
| out["expected_time_hours"] * 1.00, | |
| np.where( | |
| out["delay_ratio"] < 1.15, | |
| out["expected_time_hours"] * 1.10, | |
| out["expected_time_hours"] * 1.25, | |
| ), | |
| ), | |
| ) | |
| # Scenario pressure adds extra stress after balancing | |
| scenario_extra = (weather_sensitivity - 1.0) * 0.10 + (traffic_pressure - 1.0) * 0.08 + (capacity_pressure - 1.0) | |
| out["delivery_time_hours"] = out["delivery_time_hours"] * (1 + max(scenario_extra, -0.20)) | |
| out["expected_time_hours"] = out["expected_time_hours"].clip(lower=0.5).round(2) | |
| out["delivery_time_hours"] = out["delivery_time_hours"].clip(lower=0.5).round(2) | |
| out["delay_hours"] = (out["delivery_time_hours"] - out["expected_time_hours"]).round(2) | |
| out["calculated_delay"] = np.where(out["delay_hours"] > 0, "yes", "no") | |
| def generate_delay_score(delay): | |
| if delay <= 0: | |
| base = 5 | |
| elif delay <= 2: | |
| base = 4 | |
| elif delay <= 5: | |
| base = 3 | |
| elif delay <= 8: | |
| base = 2 | |
| else: | |
| base = 1 | |
| noise = random.choices([-1, 0, 1], weights=[1, 3, 1])[0] | |
| return int(np.clip(base + noise, 1, 5)) | |
| out["delay_score"] = out["delay_hours"].apply(generate_delay_score) | |
| out["performance_label"] = out["delay_score"].map({ | |
| 5: "Excellent", 4: "Good", 3: "Average", 2: "Poor", 1: "Critical" | |
| }) | |
| out["distance_category"] = pd.cut( | |
| out["distance_km"], | |
| bins=[0, 50, 150, 300, float("inf")], | |
| labels=["Short", "Medium", "Long", "Very Long"], | |
| include_lowest=True | |
| ) | |
| out["risk_level"] = pd.cut( | |
| out["delay_hours"], | |
| bins=[-float("inf"), 0, 2, 5, float("inf")], | |
| labels=["Low", "Moderate", "High", "Critical"] | |
| ) | |
| return out | |
| def apply_filters(df, vehicles, weather, regions, modes, max_distance): | |
| filtered = df.copy() | |
| if vehicles: | |
| filtered = filtered[filtered["vehicle_type"].isin(vehicles)] | |
| if weather: | |
| filtered = filtered[filtered["weather_condition"].isin(weather)] | |
| if regions: | |
| filtered = filtered[filtered["region"].isin(regions)] | |
| if modes: | |
| filtered = filtered[filtered["delivery_mode"].isin(modes)] | |
| filtered = filtered[filtered["distance_km"] <= max_distance] | |
| if filtered.empty: | |
| return df | |
| return filtered | |
| def metric_html(label, value, note): | |
| return f""" | |
| <div class="metric-card"> | |
| <div class="metric-label">{label}</div> | |
| <div class="metric-value">{value}</div> | |
| <div class="metric-note">{note}</div> | |
| </div> | |
| """ | |
| def generate_kpi_html(df, cleaning_report): | |
| avg_delay = df["delay_hours"].mean() | |
| delay_rate = (df["delay_hours"] > 0).mean() * 100 | |
| avg_score = df["delay_score"].mean() | |
| critical_rate = (df["risk_level"].astype(str) == "Critical").mean() * 100 | |
| total_cost = df["delivery_cost"].sum() | |
| avg_rating = df["delivery_rating"].mean() | |
| html = f""" | |
| <div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;margin-bottom:18px;"> | |
| {metric_html("Average delay", f"{avg_delay:.2f} h", "Lower is better. Negative/zero means early or on time.")} | |
| {metric_html("Delay rate", f"{delay_rate:.1f}%", "Share of deliveries where actual time exceeds expected time.")} | |
| {metric_html("Performance score", f"{avg_score:.2f}/5", "Higher score means stronger operational performance.")} | |
| {metric_html("Critical risk share", f"{critical_rate:.1f}%", "Deliveries with severe delay exposure.")} | |
| {metric_html("Total delivery cost", f"€{total_cost:,.0f}", "Total operational cost in the selected dataset.")} | |
| {metric_html("Average rating", f"{avg_rating:.2f}/5", "Customer-facing quality indicator.")} | |
| </div> | |
| <div class="insight-box"> | |
| <h3>Dataset status</h3> | |
| <p><b>{cleaning_report["final_rows"]:,}</b> rows analyzed, | |
| <b>{cleaning_report["duplicates_removed"]:,}</b> duplicates removed, | |
| <b>{cleaning_report["columns"]}</b> columns processed.</p> | |
| </div> | |
| """ | |
| return html | |
| def summary_tables(df): | |
| vehicle_perf = df.groupby("vehicle_type").agg( | |
| avg_delay=("delay_hours", "mean"), | |
| avg_score=("delay_score", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index().sort_values("avg_delay", ascending=False) | |
| weather_perf = df.groupby("weather_condition").agg( | |
| avg_delay=("delay_hours", "mean"), | |
| avg_score=("delay_score", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index().sort_values("avg_delay", ascending=False) | |
| region_perf = df.groupby("region").agg( | |
| avg_delay=("delay_hours", "mean"), | |
| avg_score=("delay_score", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index().sort_values("avg_delay", ascending=False) | |
| mode_perf = df.groupby("delivery_mode").agg( | |
| avg_delay=("delay_hours", "mean"), | |
| avg_score=("delay_score", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index().sort_values("avg_delay", ascending=False) | |
| return vehicle_perf, weather_perf, region_perf, mode_perf | |
| def make_figures(df): | |
| vehicle_perf, weather_perf, region_perf, mode_perf = summary_tables(df) | |
| fig_vehicle = px.bar( | |
| vehicle_perf, x="vehicle_type", y="avg_delay", text="avg_delay", | |
| title="Average Delay by Vehicle Type", | |
| hover_data=["avg_score", "deliveries"] | |
| ) | |
| fig_vehicle.update_traces(texttemplate="%{text:.2f}h", textposition="outside") | |
| fig_vehicle.update_layout(height=430, margin=dict(l=30, r=30, t=70, b=40)) | |
| fig_weather = px.bar( | |
| weather_perf, x="weather_condition", y="avg_delay", text="avg_delay", | |
| title="Average Delay by Weather Condition", | |
| hover_data=["avg_score", "deliveries"] | |
| ) | |
| fig_weather.update_traces(texttemplate="%{text:.2f}h", textposition="outside") | |
| fig_weather.update_layout(height=430, margin=dict(l=30, r=30, t=70, b=40)) | |
| fig_region = px.bar( | |
| region_perf, x="region", y="avg_delay", text="avg_delay", | |
| title="Average Delay by Region", | |
| hover_data=["avg_score", "deliveries"] | |
| ) | |
| fig_region.update_traces(texttemplate="%{text:.2f}h", textposition="outside") | |
| fig_region.update_layout(height=430, margin=dict(l=30, r=30, t=70, b=40)) | |
| fig_mode = px.bar( | |
| mode_perf, x="delivery_mode", y="avg_delay", text="avg_delay", | |
| title="Average Delay by Delivery Mode", | |
| hover_data=["avg_score", "deliveries"] | |
| ) | |
| fig_mode.update_traces(texttemplate="%{text:.2f}h", textposition="outside") | |
| fig_mode.update_layout(height=430, margin=dict(l=30, r=30, t=70, b=40)) | |
| fig_scatter = px.scatter( | |
| df, x="distance_km", y="delay_hours", color="risk_level", | |
| size="package_weight_kg", hover_data=["vehicle_type", "weather_condition", "region", "delivery_mode"], | |
| title="Distance, Package Weight and Delay Risk" | |
| ) | |
| fig_scatter.update_layout(height=500, margin=dict(l=30, r=30, t=70, b=40)) | |
| label_order = ["Excellent", "Good", "Average", "Poor", "Critical"] | |
| dist = df["performance_label"].value_counts().reindex(label_order).fillna(0).reset_index() | |
| dist.columns = ["performance_label", "count"] | |
| fig_perf = px.pie( | |
| dist, names="performance_label", values="count", hole=0.55, | |
| title="Performance Distribution" | |
| ) | |
| fig_perf.update_layout(height=450, margin=dict(l=30, r=30, t=70, b=40)) | |
| heat = df.pivot_table( | |
| index="weather_condition", columns="vehicle_type", | |
| values="delay_hours", aggfunc="mean" | |
| ).round(2) | |
| fig_heatmap = px.imshow( | |
| heat, text_auto=True, aspect="auto", | |
| title="Delay Risk Heatmap: Weather × Vehicle" | |
| ) | |
| fig_heatmap.update_layout(height=470, margin=dict(l=30, r=30, t=70, b=40)) | |
| cost_df = df.groupby("delivery_mode").agg( | |
| avg_cost=("delivery_cost", "mean"), | |
| avg_rating=("delivery_rating", "mean"), | |
| avg_delay=("delay_hours", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index() | |
| fig_cost = px.scatter( | |
| cost_df, x="avg_cost", y="avg_rating", size="deliveries", | |
| color="avg_delay", hover_name="delivery_mode", | |
| title="Cost vs Customer Rating by Delivery Mode" | |
| ) | |
| fig_cost.update_layout(height=470, margin=dict(l=30, r=30, t=70, b=40)) | |
| return fig_vehicle, fig_weather, fig_region, fig_mode, fig_scatter, fig_perf, fig_heatmap, fig_cost | |
| def generate_qualitative(df): | |
| vehicle_perf, weather_perf, region_perf, mode_perf = summary_tables(df) | |
| worst_vehicle = vehicle_perf.iloc[0] | |
| best_vehicle = vehicle_perf.iloc[-1] | |
| worst_weather = weather_perf.iloc[0] | |
| worst_region = region_perf.iloc[0] | |
| worst_mode = mode_perf.iloc[0] | |
| delay_rate = (df["delay_hours"] > 0).mean() * 100 | |
| avg_delay = df["delay_hours"].mean() | |
| critical_share = (df["risk_level"].astype(str) == "Critical").mean() * 100 | |
| # Detect likely main driver by comparing max-min spread | |
| spreads = { | |
| "vehicle type": vehicle_perf["avg_delay"].max() - vehicle_perf["avg_delay"].min(), | |
| "weather condition": weather_perf["avg_delay"].max() - weather_perf["avg_delay"].min(), | |
| "region": region_perf["avg_delay"].max() - region_perf["avg_delay"].min(), | |
| "delivery mode": mode_perf["avg_delay"].max() - mode_perf["avg_delay"].min(), | |
| } | |
| main_driver = max(spreads, key=spreads.get) | |
| if delay_rate < 35: | |
| overall = "The operation is relatively stable, but some segments still create avoidable delay risk." | |
| elif delay_rate < 65: | |
| overall = "The operation shows a mixed performance pattern: many deliveries are controlled, but delay risk is clearly present." | |
| else: | |
| overall = "The operation is exposed to significant delay pressure and requires active management intervention." | |
| qualitative = f""" | |
| <div class="insight-box"> | |
| <h2>Dataset-generated qualitative analysis</h2> | |
| <p><b>Overall interpretation:</b> {overall}</p> | |
| <p>The selected dataset has an average delay of <b>{avg_delay:.2f} hours</b> and a delay rate of | |
| <b>{delay_rate:.1f}%</b>. The critical-risk share is <b>{critical_share:.1f}%</b>, which indicates how much of the operation is exposed to severe service-level pressure.</p> | |
| <h3>Key operational story</h3> | |
| <p>The strongest differentiating driver in this dataset appears to be <b>{main_driver}</b>. This means management should not only look at overall delay averages, but identify which specific operational condition creates the largest performance gap.</p> | |
| <h3>Operational bottlenecks detected</h3> | |
| <ul> | |
| <li><b>Worst vehicle type:</b> {worst_vehicle["vehicle_type"]} with {worst_vehicle["avg_delay"]:.2f}h average delay.</li> | |
| <li><b>Best vehicle type:</b> {best_vehicle["vehicle_type"]} with {best_vehicle["avg_delay"]:.2f}h average delay.</li> | |
| <li><b>Highest-risk weather:</b> {worst_weather["weather_condition"]} with {worst_weather["avg_delay"]:.2f}h average delay.</li> | |
| <li><b>Highest-risk region:</b> {worst_region["region"]} with {worst_region["avg_delay"]:.2f}h average delay.</li> | |
| <li><b>Highest-risk delivery mode:</b> {worst_mode["delivery_mode"]} with {worst_mode["avg_delay"]:.2f}h average delay.</li> | |
| </ul> | |
| <h3>Business meaning</h3> | |
| <p>The dataset suggests that delivery performance is not random. Delays are connected to operational choices such as vehicle allocation, delivery mode, and route/region conditions. This is important because it means management can improve performance through targeted actions instead of treating all deliveries the same.</p> | |
| </div> | |
| """ | |
| return qualitative | |
| def generate_recommendations(df): | |
| vehicle_perf, weather_perf, region_perf, mode_perf = summary_tables(df) | |
| worst_vehicle = vehicle_perf.iloc[0]["vehicle_type"] | |
| best_vehicle = vehicle_perf.iloc[-1]["vehicle_type"] | |
| worst_weather = weather_perf.iloc[0]["weather_condition"] | |
| worst_region = region_perf.iloc[0]["region"] | |
| worst_mode = mode_perf.iloc[0]["delivery_mode"] | |
| delay_rate = (df["delay_hours"] > 0).mean() * 100 | |
| urgency = "high" if delay_rate >= 65 else "medium" if delay_rate >= 35 else "controlled" | |
| return f""" | |
| <div class="insight-box"> | |
| <h2>AI Management Recommendations</h2> | |
| <h3>Priority level: {urgency.upper()}</h3> | |
| <ol> | |
| <li><b>Reallocate vehicle capacity:</b> Increase use of <b>{best_vehicle}</b> where possible and review why <b>{worst_vehicle}</b> creates higher delay exposure.</li> | |
| <li><b>Create weather-specific routing rules:</b> Under <b>{worst_weather}</b> conditions, add buffer time, adjust promises, or prioritize safer routes.</li> | |
| <li><b>Focus regional improvement:</b> Investigate the <b>{worst_region}</b> region for congestion, route complexity, staffing gaps, or infrastructure issues.</li> | |
| <li><b>Review service promise logic:</b> <b>{worst_mode}</b> has the weakest delay performance. Management should check whether promised delivery windows are realistic.</li> | |
| <li><b>Use risk-based planning:</b> Classify deliveries before dispatch into low, moderate, high, and critical risk to allocate resources more intelligently.</li> | |
| </ol> | |
| <div class="success-box"> | |
| <b>Management conclusion:</b> The company should move from reactive delay management to predictive risk management. | |
| The dashboard helps managers identify where delays are likely to happen before they become customer-facing service failures. | |
| </div> | |
| </div> | |
| """ | |
| def create_downloads(df): | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| processed_path = f"/tmp/processed_delivery_data_{timestamp}.csv" | |
| summary_path = f"/tmp/management_summary_{timestamp}.csv" | |
| df.to_csv(processed_path, index=False) | |
| summary = [] | |
| for dimension in ["vehicle_type", "weather_condition", "region", "delivery_mode", "distance_category"]: | |
| temp = df.groupby(dimension).agg( | |
| avg_delay=("delay_hours", "mean"), | |
| avg_score=("delay_score", "mean"), | |
| deliveries=("delivery_id", "count") | |
| ).reset_index() | |
| temp.insert(0, "dimension", dimension) | |
| temp = temp.rename(columns={dimension: "category"}) | |
| summary.append(temp) | |
| pd.concat(summary, ignore_index=True).to_csv(summary_path, index=False) | |
| return processed_path, summary_path | |
| def load_options(file, weather_sensitivity, traffic_pressure, capacity_pressure): | |
| df_raw, _ = validate_and_clean(file) | |
| df = enrich_delivery_logic(df_raw, weather_sensitivity, traffic_pressure, capacity_pressure) | |
| vehicles = sorted(df["vehicle_type"].dropna().unique().tolist()) | |
| weather = sorted(df["weather_condition"].dropna().unique().tolist()) | |
| regions = sorted(df["region"].dropna().unique().tolist()) | |
| modes = sorted(df["delivery_mode"].dropna().unique().tolist()) | |
| max_distance = float(df["distance_km"].max()) | |
| return ( | |
| gr.update(choices=vehicles, value=[]), | |
| gr.update(choices=weather, value=[]), | |
| gr.update(choices=regions, value=[]), | |
| gr.update(choices=modes, value=[]), | |
| gr.update(maximum=max_distance, value=max_distance), | |
| f"✅ Dataset loaded. {len(df):,} deliveries detected. Now choose filters or click Generate Dashboard." | |
| ) | |
| def run_dashboard(file, vehicles, weather, regions, modes, max_distance, weather_sensitivity, traffic_pressure, capacity_pressure): | |
| df_raw, cleaning_report = validate_and_clean(file) | |
| df = enrich_delivery_logic(df_raw, weather_sensitivity, traffic_pressure, capacity_pressure) | |
| filtered = apply_filters(df, vehicles, weather, regions, modes, max_distance) | |
| kpi_html = generate_kpi_html(filtered, cleaning_report) | |
| figures = make_figures(filtered) | |
| qualitative = generate_qualitative(filtered) | |
| recommendations = generate_recommendations(filtered) | |
| processed_path, summary_path = create_downloads(filtered) | |
| preview_cols = [ | |
| "delivery_id", "vehicle_type", "weather_condition", "delivery_mode", "region", | |
| "distance_km", "expected_time_hours", "delivery_time_hours", "delay_hours", | |
| "delay_score", "performance_label", "risk_level" | |
| ] | |
| preview = filtered[preview_cols].head(20) | |
| return ( | |
| kpi_html, | |
| *figures, | |
| qualitative, | |
| recommendations, | |
| preview, | |
| processed_path, | |
| summary_path | |
| ) | |
| with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo", neutral_hue="slate")) as demo: | |
| gr.HTML( | |
| """ | |
| <div id="hero"> | |
| <h1>AI Delivery Performance Intelligence Dashboard</h1> | |
| <p>Upload logistics data, generate realistic delay intelligence, explore performance drivers, simulate operational pressure, and receive dataset-based management recommendations.</p> | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| file_input = gr.File(label="Upload Delivery CSV", file_types=[".csv"]) | |
| load_btn = gr.Button("Load Dataset & Activate Filters", variant="secondary") | |
| status = gr.Markdown("Upload your `Delivery_Logistics.csv` file to begin.") | |
| with gr.Column(scale=2): | |
| gr.Markdown( | |
| """ | |
| ### What this app does | |
| - Cleans and standardizes raw delivery data | |
| - Generates synthetic delivery delay intelligence | |
| - Shows KPI, quantitative, and qualitative analysis | |
| - Lets users filter by vehicle, weather, region, mode, and distance | |
| - Simulates changing weather, traffic, and capacity pressure | |
| - Exports processed data and management summaries | |
| """ | |
| ) | |
| with gr.Accordion("Interactive controls", open=True): | |
| with gr.Row(): | |
| vehicle_filter = gr.Dropdown(label="Filter by vehicle type", choices=[], multiselect=True) | |
| weather_filter = gr.Dropdown(label="Filter by weather condition", choices=[], multiselect=True) | |
| region_filter = gr.Dropdown(label="Filter by region", choices=[], multiselect=True) | |
| mode_filter = gr.Dropdown(label="Filter by delivery mode", choices=[], multiselect=True) | |
| with gr.Row(): | |
| distance_filter = gr.Slider(label="Maximum distance in km", minimum=0, maximum=500, value=500, step=1) | |
| weather_sensitivity = gr.Slider(label="Weather sensitivity scenario", minimum=0.5, maximum=2.0, value=1.0, step=0.1) | |
| traffic_pressure = gr.Slider(label="Traffic / region pressure scenario", minimum=0.5, maximum=2.0, value=1.0, step=0.1) | |
| capacity_pressure = gr.Slider(label="Capacity pressure scenario", minimum=0.8, maximum=1.4, value=1.0, step=0.05) | |
| generate_btn = gr.Button("Generate Dashboard", variant="primary", size="lg") | |
| with gr.Tab("1. KPI Overview"): | |
| kpi_output = gr.HTML() | |
| preview_table = gr.Dataframe(label="Preview of processed delivery intelligence", interactive=False, wrap=True) | |
| with gr.Tab("2. Quantitative Analysis"): | |
| with gr.Row(): | |
| fig_vehicle = gr.Plot() | |
| fig_weather = gr.Plot() | |
| with gr.Row(): | |
| fig_region = gr.Plot() | |
| fig_mode = gr.Plot() | |
| with gr.Row(): | |
| fig_scatter = gr.Plot() | |
| fig_perf = gr.Plot() | |
| with gr.Row(): | |
| fig_heatmap = gr.Plot() | |
| fig_cost = gr.Plot() | |
| with gr.Tab("3. Qualitative Analysis"): | |
| qualitative_output = gr.HTML() | |
| with gr.Tab("4. AI Management Recommendations"): | |
| recommendations_output = gr.HTML() | |
| with gr.Row(): | |
| processed_download = gr.File(label="Download processed dataset") | |
| summary_download = gr.File(label="Download management summary") | |
| load_btn.click( | |
| load_options, | |
| inputs=[file_input, weather_sensitivity, traffic_pressure, capacity_pressure], | |
| outputs=[vehicle_filter, weather_filter, region_filter, mode_filter, distance_filter, status] | |
| ) | |
| generate_btn.click( | |
| run_dashboard, | |
| inputs=[ | |
| file_input, vehicle_filter, weather_filter, region_filter, mode_filter, | |
| distance_filter, weather_sensitivity, traffic_pressure, capacity_pressure | |
| ], | |
| outputs=[ | |
| kpi_output, | |
| fig_vehicle, fig_weather, fig_region, fig_mode, | |
| fig_scatter, fig_perf, fig_heatmap, fig_cost, | |
| qualitative_output, recommendations_output, | |
| preview_table, processed_download, summary_download | |
| ] | |
| ) | |
| demo.launch() | |