AdrienVDS05 commited on
Commit
1d494a3
Β·
verified Β·
1 Parent(s): 6ee7177

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -0
app.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import plotly.graph_objects as go
4
+
5
+ # ── load data ──────────────────────────────────────────────
6
+ df = pd.read_csv("restaurant_master_scored.csv")
7
+
8
+ CUISINES = ["All"] + sorted(df["cuisine_type"].unique().tolist())
9
+ PRICES = ["All"] + ["€", "€€", "€€€", "€€€€"]
10
+ ARRONDS = ["All"] + sorted(df["arrondissement"].unique().tolist())
11
+
12
+ LABEL_COLOR = {"Worth It": "#1A6B3C", "Maybe": "#7D5A00", "Skip It": "#8B1A1A"}
13
+ LABEL_BG = {"Worth It": "#EAF3DE", "Maybe": "#FAEEDA", "Skip It": "#FCEBEB"}
14
+
15
+ # ── score component chart ──────────────────────────────────
16
+ def make_chart(row):
17
+ components = {
18
+ "Sentiment adj": round(row["sentiment_adj_norm"] * 0.35 * 100, 1),
19
+ "Price fairness": round(row["price_fairness"] * 0.25 * 100, 1),
20
+ "Consistency": round(row["consistency_index"] * 0.20 * 100, 1),
21
+ "Trust": round(row["trust_score"] * 0.10 * 100, 1),
22
+ "Divergence βˆ’": round(-row["star_sentiment_divergence"] * 0.10 * 100, 1),
23
+ }
24
+ colors = ["#3D2B8E", "#7B6FCA", "#1A6B3C", "#7D5A00", "#8B1A1A"]
25
+ fig = go.Figure(go.Bar(
26
+ x=list(components.values()),
27
+ y=list(components.keys()),
28
+ orientation="h",
29
+ marker_color=colors,
30
+ text=[f"{v:+.1f}" for v in components.values()],
31
+ textposition="outside",
32
+ ))
33
+ fig.update_layout(
34
+ margin=dict(l=10, r=60, t=10, b=10),
35
+ height=220,
36
+ xaxis=dict(showgrid=False, zeroline=True,
37
+ zerolinecolor="#cccccc", title="Points"),
38
+ yaxis=dict(showgrid=False),
39
+ paper_bgcolor="rgba(0,0,0,0)",
40
+ plot_bgcolor="rgba(0,0,0,0)",
41
+ font=dict(size=12),
42
+ )
43
+ return fig
44
+
45
+ # ── AI explanation (rule-based, no API key needed) ─────────
46
+ def explain(row):
47
+ name = row["restaurant_name"]
48
+ label = row["worth_it_label"]
49
+ score = row["true_score"]
50
+ price = row["price_category"]
51
+ sent = row["sentiment_score"]
52
+ stars = row["star_rating"]
53
+ vols = row["sentiment_volatility"]
54
+ pf = row["price_fairness"]
55
+
56
+ sentiment_word = "positive" if sent > 0.2 else ("neutral" if sent > -0.2 else "negative")
57
+ volatility_note = (
58
+ " Reviews are polarised β€” strong opinions on both sides."
59
+ if vols > 0.5 else
60
+ " Reviews are consistent."
61
+ )
62
+ value_note = (
63
+ f" At {price}, it delivers excellent value for money."
64
+ if pf > 0.6 else
65
+ f" At {price}, the price-to-experience ratio is fair."
66
+ if pf > 0.3 else
67
+ f" At {price}, some diners feel it falls short of expectations."
68
+ )
69
+
70
+ if label == "Worth It":
71
+ verdict = f"This restaurant scores {score:.0f}/100 and is genuinely worth a visit."
72
+ elif label == "Maybe":
73
+ verdict = f"This restaurant scores {score:.0f}/100 β€” a solid option depending on your preferences."
74
+ else:
75
+ verdict = f"This restaurant scores {score:.0f}/100 β€” consider alternatives before booking."
76
+
77
+ return (
78
+ f"**{name}** β€” _{label}_\n\n"
79
+ f"{verdict} "
80
+ f"Customer reviews are broadly {sentiment_word} (raw sentiment: {sent:+.2f}), "
81
+ f"and the star rating is {stars:.1f}/5.{volatility_note}{value_note}"
82
+ )
83
+
84
+ # ── main function ──────────────────────────────────────────
85
+ def search(cuisine, price, arrond, top_n):
86
+ filtered = df.copy()
87
+ if cuisine != "All":
88
+ filtered = filtered[filtered["cuisine_type"] == cuisine]
89
+ if price != "All":
90
+ filtered = filtered[filtered["price_category"] == price]
91
+ if arrond != "All":
92
+ filtered = filtered[filtered["arrondissement"] == arrond]
93
+
94
+ if filtered.empty:
95
+ return "No restaurants match your filters.", None, ""
96
+
97
+ top = filtered.nlargest(int(top_n), "true_score").reset_index(drop=True)
98
+ best = top.iloc[0]
99
+
100
+ # Results table
101
+ display = top[[
102
+ "restaurant_name", "cuisine_type", "arrondissement",
103
+ "price_category", "star_rating", "true_score", "worth_it_label"
104
+ ]].rename(columns={
105
+ "restaurant_name": "Name",
106
+ "cuisine_type": "Cuisine",
107
+ "arrondissement": "Area",
108
+ "price_category": "Price",
109
+ "star_rating": "Stars",
110
+ "true_score": "True Score",
111
+ "worth_it_label": "Verdict",
112
+ })
113
+
114
+ chart = make_chart(best)
115
+ explanation = explain(best)
116
+
117
+ return display, chart, explanation
118
+
119
+ # ── interface ──────────────────────────────────────────────
120
+ with gr.Blocks(title="Restaurant Worth-It Score β€” Paris") as demo:
121
+ gr.Markdown("## Restaurant Worth-It Score β€” Paris\n"
122
+ "AI-powered ratings that go beyond the stars. "
123
+ "Select your filters to find restaurants that truly deliver.")
124
+
125
+ with gr.Row():
126
+ cuisine_dd = gr.Dropdown(CUISINES, value="All", label="Cuisine type")
127
+ price_dd = gr.Dropdown(PRICES, value="All", label="Price tier")
128
+ arrond_dd = gr.Dropdown(ARRONDS, value="All", label="Arrondissement")
129
+ top_n_sl = gr.Slider(3, 20, value=10, step=1, label="Results to show")
130
+
131
+ search_btn = gr.Button("Find restaurants", variant="primary")
132
+
133
+ gr.Markdown("### Top results")
134
+ results_table = gr.Dataframe(label="Ranked restaurants", interactive=False)
135
+
136
+ with gr.Row():
137
+ with gr.Column(scale=1):
138
+ gr.Markdown("#### Score breakdown β€” top result")
139
+ score_chart = gr.Plot(label="")
140
+ with gr.Column(scale=1):
141
+ gr.Markdown("#### AI explanation β€” top result")
142
+ explanation_box = gr.Markdown()
143
+
144
+ search_btn.click(
145
+ fn=search,
146
+ inputs=[cuisine_dd, price_dd, arrond_dd, top_n_sl],
147
+ outputs=[results_table, score_chart, explanation_box],
148
+ )
149
+
150
+ demo.launch()