import os import requests import pandas as pd import gradio as gr import plotly.graph_objects as go N8N_WEBHOOK_URL = os.environ.get("N8N_WEBHOOK_URL", "").strip() def make_demo_df(): return pd.DataFrame([ ["Paris", "E-Scooter", 4.6, 4.1, 0.12, 0.06], ["Paris", "E-Bike", 4.3, 4.2, 0.14, 0.05], ["Berlin", "E-Scooter", 4.9, 3.8, 0.05, 0.08], ["Berlin", "E-Bike", 4.5, 4.0, 0.09, 0.06], ["Madrid", "E-Scooter", 4.2, 4.3, 0.17, 0.05], ["Warsaw", "Shared-EV", 5.0, 4.0, 0.07, 0.05], ], columns=[ "city", "vehicle_type", "avg_final_price_eur", "avg_rating", "avg_sentiment", "cancellation_rate" ]) def load_data(): try: df = pd.read_csv("merged_summary.csv") except Exception: return make_demo_df() df.columns = [str(c).strip() for c in df.columns] rename_map = {} for c in df.columns: cl = c.lower().strip() if cl == "city": rename_map[c] = "city" elif cl in ["vehicle_type", "ride_type", "vehicle", "vehicletype"]: rename_map[c] = "vehicle_type" elif cl in ["avg_final_price_eur", "final_price_eur", "avg_price", "avg_final_price", "price"]: rename_map[c] = "avg_final_price_eur" elif cl in ["avg_rating", "rating", "avg_star_rating", "star_rating"]: rename_map[c] = "avg_rating" elif cl in ["avg_sentiment", "sentiment", "compound", "vader_compound", "avg_compound_score"]: rename_map[c] = "avg_sentiment" elif cl in ["cancellation_rate", "cancel_rate", "avg_cancellation_rate"]: rename_map[c] = "cancellation_rate" df = df.rename(columns=rename_map) required = [ "city", "vehicle_type", "avg_final_price_eur", "avg_rating", "avg_sentiment", "cancellation_rate", ] for col in required: if col not in df.columns: if col in ["city", "vehicle_type"]: df[col] = "Unknown" else: df[col] = 0.0 for col in ["avg_final_price_eur", "avg_rating", "avg_sentiment", "cancellation_rate"]: df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0) return df def render_dashboard(city, vehicle): df = load_data().copy() if city != "All": df = df[df["city"] == city] if vehicle != "All": df = df[df["vehicle_type"] == vehicle] if df.empty: empty = go.Figure() empty.update_layout(title="No data for selected filters") return "### No data available", empty, empty, empty avg_price = df["avg_final_price_eur"].mean() avg_rating = df["avg_rating"].mean() avg_cancel = df["cancellation_rate"].mean() kpi = f""" ### KPIs - Avg Price: EUR {avg_price:.2f} - Avg Rating: {avg_rating:.2f} - Cancellation Rate: {avg_cancel:.2%} """ seg = df.groupby(["city", "vehicle_type"], as_index=False).agg( avg_final_price_eur=("avg_final_price_eur", "mean"), avg_rating=("avg_rating", "mean"), cancellation_rate=("cancellation_rate", "mean"), ) fig1 = go.Figure() fig1.add_bar( x=[f"{r['city']} - {r['vehicle_type']}" for _, r in seg.iterrows()], y=seg["avg_final_price_eur"] ) fig1.update_layout(title="Average Price by City / Vehicle") fig2 = go.Figure() fig2.add_bar( x=[f"{r['city']} - {r['vehicle_type']}" for _, r in seg.iterrows()], y=seg["avg_rating"] ) fig2.update_layout(title="Average Rating by City / Vehicle") city_agg = df.groupby("city", as_index=False).agg( cancellation_rate=("cancellation_rate", "mean") ) fig3 = go.Figure() fig3.add_bar( x=city_agg["city"], y=city_agg["cancellation_rate"] ) fig3.update_layout(title="Cancellation Rate by City") return kpi, fig1, fig2, fig3 def predict(price, discount): score = 0.5 if price < 5: score += 0.2 if discount > 10: score += 0.1 score = min(max(score, 0), 1) return { "satisfaction_probability": round(score, 2), "label": "High" if score > 0.5 else "Low" } def get_n8n_recommendation(city, vehicle): if not N8N_WEBHOOK_URL: return {"error": "N8N_WEBHOOK_URL is not configured in Hugging Face secrets."} payload = { "city": city, "vehicle_type": vehicle } try: response = requests.post(N8N_WEBHOOK_URL, json=payload, timeout=20) response.raise_for_status() try: return response.json() except Exception: return { "error": "n8n did not return valid JSON", "status_code": response.status_code, "raw_response": response.text, "payload_sent": payload } except Exception as e: return { "error": str(e), "payload_sent": payload } with gr.Blocks() as demo: gr.Markdown("# Urban Mobility App") with gr.Tab("Dashboard"): city = gr.Dropdown( ["All", "Paris", "Berlin", "Madrid", "Warsaw", "Turin"], value="All", label="City" ) vehicle = gr.Dropdown( ["All", "E-Scooter", "E-Bike", "Shared-EV", "Bus-Connect"], value="All", label="Vehicle" ) btn = gr.Button("Refresh") kpi = gr.Markdown() chart1 = gr.Plot() chart2 = gr.Plot() chart3 = gr.Plot() btn.click( render_dashboard, inputs=[city, vehicle], outputs=[kpi, chart1, chart2, chart3] ) with gr.Tab("Prediction"): price = gr.Number(label="Price", value=4.0) discount = gr.Number(label="Discount %", value=10) btn2 = gr.Button("Predict") out = gr.JSON() btn2.click(predict, inputs=[price, discount], outputs=out) with gr.Tab("n8n Recommendation"): n8n_city = gr.Dropdown( ["Paris", "Berlin", "Madrid", "Warsaw", "Turin"], value="Berlin", label="City" ) n8n_vehicle = gr.Dropdown( ["E-Scooter", "E-Bike", "Shared-EV", "Bus-Connect"], value="E-Scooter", label="Vehicle" ) n8n_btn = gr.Button("Get n8n Recommendation") n8n_output = gr.JSON(label="n8n Response") n8n_btn.click( get_n8n_recommendation, inputs=[n8n_city, n8n_vehicle], outputs=[n8n_output] ) if __name__ == "__main__": demo.launch()