Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| # ββ load data ββββββββββββββββββββββββββββββββββββββββββββββ | |
| df = pd.read_csv("restaurant_master_scored.csv") | |
| CUISINES = ["All"] + sorted(df["cuisine_type"].unique().tolist()) | |
| PRICES = ["All"] + ["β¬", "β¬β¬", "β¬β¬β¬", "β¬β¬β¬β¬"] | |
| ARRONDS = ["All"] + sorted(df["arrondissement"].unique().tolist()) | |
| LABEL_COLOR = {"Worth It": "#1A6B3C", "Maybe": "#7D5A00", "Skip It": "#8B1A1A"} | |
| LABEL_BG = {"Worth It": "#EAF3DE", "Maybe": "#FAEEDA", "Skip It": "#FCEBEB"} | |
| # ββ charts ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def make_chart(row, filtered_df): | |
| # First chart: score component chart for top result | |
| components = { | |
| "Sentiment adj": round(row["sentiment_adj_norm"] * 0.35 * 100, 1), | |
| "Price fairness": round(row["price_fairness"] * 0.25 * 100, 1), | |
| "Consistency": round(row["consistency_index"] * 0.20 * 100, 1), | |
| "Trust": round(row["trust_score"] * 0.10 * 100, 1), | |
| "Divergence β": round(-row["star_sentiment_divergence"] * 0.10 * 100, 1), | |
| } | |
| colors = ["#3D2B8E", "#7B6FCA", "#1A6B3C", "#7D5A00", "#8B1A1A"] | |
| fig_bar = go.Figure( | |
| go.Bar( | |
| x=list(components.values()), | |
| y=list(components.keys()), | |
| orientation="h", | |
| marker_color=colors, | |
| text=[f"{v:+.1f}" for v in components.values()], | |
| textposition="outside", | |
| ) | |
| ) | |
| fig_bar.update_layout( | |
| margin=dict(l=10, r=60, t=10, b=10), | |
| height=220, | |
| xaxis=dict( | |
| showgrid=False, | |
| zeroline=True, | |
| zerolinecolor="#cccccc", | |
| title="Points", | |
| ), | |
| yaxis=dict(showgrid=False), | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font=dict(size=12), | |
| ) | |
| # Second chart: scatter plot of all filtered restaurants | |
| fig_scatter = go.Figure() | |
| for label in ["Worth It", "Maybe", "Skip It"]: | |
| subset = filtered_df[filtered_df["worth_it_label"] == label] | |
| if not subset.empty: | |
| fig_scatter.add_trace( | |
| go.Scatter( | |
| x=subset["star_rating"], | |
| y=subset["true_score"], | |
| mode="markers", | |
| name=label, | |
| marker=dict( | |
| size=10, | |
| color=LABEL_COLOR.get(label, "#666666"), | |
| line=dict(width=1, color="white"), | |
| ), | |
| text=subset["restaurant_name"], | |
| hovertemplate=( | |
| "<b>%{text}</b><br>" | |
| "Stars: %{x:.1f}<br>" | |
| "True Score: %{y:.1f}<br>" | |
| "Verdict: " + label + | |
| "<extra></extra>" | |
| ), | |
| ) | |
| ) | |
| fig_scatter.update_layout( | |
| margin=dict(l=10, r=10, t=40, b=10), | |
| height=320, | |
| xaxis=dict(title="Star rating", showgrid=True, gridcolor="#eeeeee"), | |
| yaxis=dict(title="True score", showgrid=True, gridcolor="#eeeeee"), | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| font=dict(size=12), | |
| legend=dict(orientation="h", y=1.12, x=0), | |
| ) | |
| return fig_bar, fig_scatter | |
| # ββ AI explanation (rule-based, no API key needed) βββββββββ | |
| import requests | |
| def explain(row): | |
| payload = { | |
| "restaurant_name": row["restaurant_name"], | |
| "true_score": float(row["true_score"]), | |
| "worth_it_label": row["worth_it_label"], | |
| "sentiment_score": float(row["sentiment_score"]), | |
| "star_rating": float(row["star_rating"]), | |
| "price_category": row["price_category"], | |
| } | |
| try: | |
| r = requests.post( | |
| "https://scohen3012.app.n8n.cloud/webhook/explain", | |
| json=payload, | |
| timeout=8 | |
| ) | |
| return r.json().get("explanation", "No explanation returned.") | |
| except Exception as e: | |
| return f"Explanation service unavailable: {e}" | |
| # ββ main function ββββββββββββββββββββββββββββββββββββββββββ | |
| def search(cuisine, price, arrond, top_n): | |
| filtered = df.copy() | |
| if cuisine != "All": | |
| filtered = filtered[filtered["cuisine_type"] == cuisine] | |
| if price != "All": | |
| filtered = filtered[filtered["price_category"] == price] | |
| if arrond != "All": | |
| filtered = filtered[filtered["arrondissement"] == arrond] | |
| if filtered.empty: | |
| empty_fig = go.Figure() | |
| return "No restaurants match your filters.", empty_fig, empty_fig, "" | |
| top = filtered.nlargest(int(top_n), "true_score").reset_index(drop=True) | |
| best = top.iloc[0] | |
| # Results table | |
| display = top[ | |
| [ | |
| "restaurant_name", | |
| "cuisine_type", | |
| "arrondissement", | |
| "price_category", | |
| "star_rating", | |
| "true_score", | |
| "worth_it_label", | |
| ] | |
| ].rename( | |
| columns={ | |
| "restaurant_name": "Name", | |
| "cuisine_type": "Cuisine", | |
| "arrondissement": "Area", | |
| "price_category": "Price", | |
| "star_rating": "Stars", | |
| "true_score": "True Score", | |
| "worth_it_label": "Verdict", | |
| } | |
| ) | |
| score_chart, scatter_chart = make_chart(best, top) | |
| explanation = explain(best) | |
| return display, score_chart, scatter_chart, explanation | |
| # ββ interface ββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(title="Restaurant Worth-It Score β Paris") as demo: | |
| gr.Markdown( | |
| "## Restaurant Worth-It Score β Paris\n" | |
| "AI-powered ratings that go beyond the stars. " | |
| "Select your filters to find restaurants that truly deliver." | |
| ) | |
| with gr.Row(): | |
| cuisine_dd = gr.Dropdown(CUISINES, value="All", label="Cuisine type") | |
| price_dd = gr.Dropdown(PRICES, value="All", label="Price tier") | |
| arrond_dd = gr.Dropdown(ARRONDS, value="All", label="Arrondissement") | |
| top_n_sl = gr.Slider(3, 20, value=10, step=1, label="Results to show") | |
| search_btn = gr.Button("Find restaurants", variant="primary") | |
| gr.Markdown("### Top results") | |
| results_table = gr.Dataframe(label="Ranked restaurants", interactive=False) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("#### Score breakdown β top result") | |
| score_chart = gr.Plot(label="") | |
| with gr.Column(scale=1): | |
| gr.Markdown("#### All filtered restaurants") | |
| scatter_chart = gr.Plot(label="") | |
| gr.Markdown("#### AI explanation β top result") | |
| explanation_box = gr.Markdown() | |
| search_btn.click( | |
| fn=search, | |
| inputs=[cuisine_dd, price_dd, arrond_dd, top_n_sl], | |
| outputs=[results_table, score_chart, scatter_chart, explanation_box], | |
| ) | |
| demo.launch() |