Osole7 commited on
Commit
166d616
·
verified ·
1 Parent(s): bf90a51

Upload 5 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ restaurants_synthetic_dataset.csv filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,10 +1,19 @@
1
  ---
2
- title: D6-SE21 GROUP PROJECT2
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: docker
 
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Restaurant Insight Dashboard
3
+ emoji: 🍽️
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 4.44.1
8
+ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Restaurant Insight Dashboard
13
+
14
+ This Hugging Face Space lets users select a restaurant and view:
15
+ - customer review metrics
16
+ - sanitary reference level
17
+ - average Google rating
18
+ - revenue trend
19
+ - a simple managerial recommendation
app.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import gradio as gr
3
+ import matplotlib.pyplot as plt
4
+ from pathlib import Path
5
+
6
+ BASE_DIR = Path(__file__).resolve().parent if '__file__' in globals() else Path('.')
7
+ DATA_FILE = BASE_DIR / 'restaurants_synthetic_dataset.csv'
8
+ SUMMARY_FILE = BASE_DIR / 'restaurants_synthetic_reviews_summary.csv'
9
+
10
+ # Load data once at startup
11
+ business_df = pd.read_csv(DATA_FILE, low_memory=False)
12
+ summary_df = pd.read_csv(SUMMARY_FILE, low_memory=False)
13
+
14
+ # Clean types
15
+ business_df['date'] = pd.to_datetime(business_df['date'], errors='coerce')
16
+ business_df['chiffre_affaire_eur'] = pd.to_numeric(business_df['chiffre_affaire_eur'], errors='coerce')
17
+ business_df['google_rating'] = pd.to_numeric(business_df['google_rating'], errors='coerce')
18
+
19
+ numeric_cols = [
20
+ 'nb_reviews', 'note_review_moyenne', 'note_review_min', 'note_review_max',
21
+ 'part_reviews_positives', 'part_reviews_mitigees', 'part_reviews_negatives'
22
+ ]
23
+ for col in numeric_cols:
24
+ summary_df[col] = pd.to_numeric(summary_df[col], errors='coerce')
25
+
26
+ # Create restaurant choices
27
+ restaurant_choices = sorted(summary_df['restaurant_nom'].dropna().astype(str).unique().tolist())
28
+
29
+
30
+ def safe_str(value, fallback='N/A'):
31
+ if pd.isna(value):
32
+ return fallback
33
+ text = str(value).strip()
34
+ return text if text else fallback
35
+
36
+
37
+ def recommendation_text(avg_review, neg_share, sanitary_level, google_rating):
38
+ issues = []
39
+ strengths = []
40
+
41
+ if pd.notna(avg_review):
42
+ if avg_review >= 4.3:
43
+ strengths.append('very strong customer satisfaction')
44
+ elif avg_review >= 3.8:
45
+ strengths.append('solid customer satisfaction')
46
+ else:
47
+ issues.append('customer satisfaction is below the target level')
48
+
49
+ if pd.notna(neg_share):
50
+ if neg_share >= 0.30:
51
+ issues.append('negative reviews are relatively high')
52
+ elif neg_share <= 0.10:
53
+ strengths.append('negative reviews remain low')
54
+
55
+ sanitary_text = safe_str(sanitary_level, '').lower()
56
+ if 'à améliorer' in sanitary_text or 'ameliorer' in sanitary_text:
57
+ issues.append('sanitary reference level suggests improvement is needed')
58
+ elif sanitary_text:
59
+ strengths.append(f'sanitary status is {safe_str(sanitary_level)}')
60
+
61
+ if pd.notna(google_rating):
62
+ if google_rating >= 4.3:
63
+ strengths.append('google rating is strong')
64
+ elif google_rating < 4.0:
65
+ issues.append('google rating could be improved')
66
+
67
+ if issues and strengths:
68
+ return (
69
+ 'Recommendation: monitor this restaurant closely. It shows some positive signals, but the priority '
70
+ 'should be to improve service consistency and reduce the sources of negative reviews. '
71
+ f"Strengths include {', '.join(strengths[:2])}. Main issues: {', '.join(issues[:2])}."
72
+ )
73
+ if issues:
74
+ return (
75
+ 'Recommendation: improvement plan needed. Focus first on the most visible weaknesses in customer '
76
+ f"experience and operations. Main issues: {', '.join(issues[:3])}."
77
+ )
78
+ return (
79
+ 'Recommendation: maintain current performance and continue monitoring quality. '
80
+ f"Main strengths: {', '.join(strengths[:3]) if strengths else 'overall stable performance'}."
81
+ )
82
+
83
+
84
+ def build_chart(restaurant_name):
85
+ subset = business_df[business_df['restaurant_nom'] == restaurant_name].copy()
86
+ subset = subset.sort_values('date')
87
+ fig, ax = plt.subplots(figsize=(7, 4))
88
+ ax.plot(subset['date'], subset['chiffre_affaire_eur'], marker='o')
89
+ ax.set_title(f'Revenue trend - {restaurant_name}')
90
+ ax.set_xlabel('Date')
91
+ ax.set_ylabel('Revenue (EUR)')
92
+ plt.xticks(rotation=45)
93
+ plt.tight_layout()
94
+ return fig
95
+
96
+
97
+ def analyze_restaurant(restaurant_name):
98
+ if not restaurant_name:
99
+ return 'Please choose a restaurant.', 'No data yet.', None
100
+
101
+ review_rows = summary_df[summary_df['restaurant_nom'] == restaurant_name].copy()
102
+ business_rows = business_df[business_df['restaurant_nom'] == restaurant_name].copy()
103
+
104
+ if review_rows.empty and business_rows.empty:
105
+ return f'No data found for {restaurant_name}.', 'No data available.', None
106
+
107
+ review_row = review_rows.iloc[0] if not review_rows.empty else pd.Series(dtype=object)
108
+
109
+ city = safe_str(review_row.get('ville'))
110
+ price_range = safe_str(review_row.get('gamme_prix'))
111
+ sanitary = safe_str(review_row.get('niveau_sanitaire_reference'))
112
+ nb_reviews = review_row.get('nb_reviews')
113
+ avg_review = review_row.get('note_review_moyenne')
114
+ pos_share = review_row.get('part_reviews_positives')
115
+ mixed_share = review_row.get('part_reviews_mitigees')
116
+ neg_share = review_row.get('part_reviews_negatives')
117
+
118
+ type_restauration = safe_str(business_rows['type_restauration'].mode().iloc[0] if not business_rows.empty and not business_rows['type_restauration'].mode().empty else None)
119
+ avg_google_rating = business_rows['google_rating'].mean() if not business_rows.empty else None
120
+ avg_revenue = business_rows['chiffre_affaire_eur'].mean() if not business_rows.empty else None
121
+ latest_revenue = business_rows.sort_values('date')['chiffre_affaire_eur'].dropna().iloc[-1] if not business_rows.empty and business_rows['chiffre_affaire_eur'].notna().any() else None
122
+
123
+ overview = f"""
124
+ ## Restaurant Overview
125
+ - **Name:** {restaurant_name}
126
+ - **City:** {city}
127
+ - **Type:** {type_restauration}
128
+ - **Price range:** {price_range}
129
+ - **Sanitary reference:** {sanitary}
130
+ - **Number of reviews:** {int(nb_reviews) if pd.notna(nb_reviews) else 'N/A'}
131
+ - **Average review score:** {avg_review:.2f}/5
132
+ - **Average Google rating:** {avg_google_rating:.2f}/5 if pd.notna(avg_google_rating) else N/A
133
+ - **Average monthly revenue:** {avg_revenue:,.0f} EUR if pd.notna(avg_revenue) else N/A
134
+ - **Latest revenue observed:** {latest_revenue:,.0f} EUR if pd.notna(latest_revenue) else N/A
135
+ """
136
+
137
+ # Fix conditional formatting in markdown text
138
+ overview = overview.replace(
139
+ f"{avg_google_rating:.2f}/5 if pd.notna(avg_google_rating) else N/A",
140
+ f"{avg_google_rating:.2f}/5" if pd.notna(avg_google_rating) else 'N/A'
141
+ )
142
+ overview = overview.replace(
143
+ f"{avg_revenue:,.0f} EUR if pd.notna(avg_revenue) else N/A",
144
+ f"{avg_revenue:,.0f} EUR" if pd.notna(avg_revenue) else 'N/A'
145
+ )
146
+ overview = overview.replace(
147
+ f"{latest_revenue:,.0f} EUR if pd.notna(latest_revenue) else N/A",
148
+ f"{latest_revenue:,.0f} EUR" if pd.notna(latest_revenue) else 'N/A'
149
+ )
150
+
151
+ insight = f"""
152
+ ## Review Insight
153
+ - **Positive reviews:** {pos_share * 100:.1f}%
154
+ - **Mixed reviews:** {mixed_share * 100:.1f}%
155
+ - **Negative reviews:** {neg_share * 100:.1f}%
156
+
157
+ ## Recommendation
158
+ {recommendation_text(avg_review, neg_share, sanitary, avg_google_rating)}
159
+ """
160
+
161
+ fig = build_chart(restaurant_name) if not business_rows.empty else None
162
+ return overview, insight, fig
163
+
164
+
165
+ DESCRIPTION = """
166
+ Choose a restaurant to view its customer review profile, business indicators, and a simple managerial recommendation.
167
+ This app combines review summary data and synthetic business performance data.
168
+ """
169
+
170
+ with gr.Blocks() as demo:
171
+ gr.Markdown('# Restaurant Insight Dashboard')
172
+ gr.Markdown(DESCRIPTION)
173
+
174
+ with gr.Row():
175
+ with gr.Column(scale=1):
176
+ restaurant_input = gr.Dropdown(
177
+ choices=restaurant_choices,
178
+ label='Restaurant name',
179
+ value=restaurant_choices[0] if restaurant_choices else None,
180
+ filterable=True,
181
+ )
182
+ analyze_btn = gr.Button('Analyze restaurant')
183
+ with gr.Column(scale=2):
184
+ overview_output = gr.Markdown(label='Overview')
185
+ insight_output = gr.Markdown(label='Insight')
186
+
187
+ revenue_plot = gr.Plot(label='Revenue trend')
188
+
189
+ analyze_btn.click(
190
+ fn=analyze_restaurant,
191
+ inputs=restaurant_input,
192
+ outputs=[overview_output, insight_output, revenue_plot],
193
+ )
194
+
195
+ demo.load(
196
+ fn=analyze_restaurant,
197
+ inputs=restaurant_input,
198
+ outputs=[overview_output, insight_output, revenue_plot],
199
+ )
200
+
201
+ if __name__ == '__main__':
202
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0.0
2
+ pandas>=2.0.0
3
+ matplotlib>=3.7.0
restaurants_synthetic_dataset.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e5281618241706cf2c016d102b1841f09f08083c37332daf6e022e2a86ea6a62
3
+ size 41403049
restaurants_synthetic_reviews_summary.csv ADDED
The diff for this file is too large to render. See raw diff