Spaces:
Runtime error
Runtime error
| import pandas as pd | |
| import gradio as gr | |
| import matplotlib.pyplot as plt | |
| from pathlib import Path | |
| BASE_DIR = Path(__file__).resolve().parent | |
| DATA_FILE = BASE_DIR / "restaurants_synthetic_dataset.csv" | |
| SUMMARY_FILE = BASE_DIR / "restaurants_synthetic_reviews_summary.csv" | |
| business_df = pd.read_csv(DATA_FILE, low_memory=False) | |
| summary_df = pd.read_csv(SUMMARY_FILE, low_memory=False) | |
| business_df["date"] = pd.to_datetime(business_df["date"], errors="coerce") | |
| business_df["chiffre_affaire_eur"] = pd.to_numeric(business_df["chiffre_affaire_eur"], errors="coerce") | |
| business_df["google_rating"] = pd.to_numeric(business_df["google_rating"], errors="coerce") | |
| numeric_cols = [ | |
| "nb_reviews", | |
| "note_review_moyenne", | |
| "note_review_min", | |
| "note_review_max", | |
| "part_reviews_positives", | |
| "part_reviews_mitigees", | |
| "part_reviews_negatives", | |
| ] | |
| for col in numeric_cols: | |
| if col in summary_df.columns: | |
| summary_df[col] = pd.to_numeric(summary_df[col], errors="coerce") | |
| restaurant_choices = sorted(summary_df["restaurant_nom"].dropna().astype(str).unique().tolist()) | |
| def safe_str(value, fallback="N/A"): | |
| if pd.isna(value): | |
| return fallback | |
| text = str(value).strip() | |
| return text if text else fallback | |
| def recommendation_text(avg_review, neg_share, sanitary_level, google_rating): | |
| issues = [] | |
| strengths = [] | |
| if pd.notna(avg_review): | |
| if avg_review >= 4.3: | |
| strengths.append("very strong customer satisfaction") | |
| elif avg_review >= 3.8: | |
| strengths.append("solid customer satisfaction") | |
| else: | |
| issues.append("customer satisfaction is below the target level") | |
| if pd.notna(neg_share): | |
| if neg_share >= 0.30: | |
| issues.append("negative reviews are relatively high") | |
| elif neg_share <= 0.10: | |
| strengths.append("negative reviews remain low") | |
| sanitary_text = safe_str(sanitary_level, "").lower() | |
| if "à améliorer" in sanitary_text or "ameliorer" in sanitary_text: | |
| issues.append("sanitary reference level suggests improvement is needed") | |
| elif sanitary_text: | |
| strengths.append(f"sanitary status is {safe_str(sanitary_level)}") | |
| if pd.notna(google_rating): | |
| if google_rating >= 4.3: | |
| strengths.append("google rating is strong") | |
| elif google_rating < 4.0: | |
| issues.append("google rating could be improved") | |
| if issues and strengths: | |
| return ( | |
| "Monitor this restaurant closely. It shows positive signals, but priority should be given " | |
| f"to improving weak points. Strengths: {', '.join(strengths[:2])}. " | |
| f"Main issues: {', '.join(issues[:2])}." | |
| ) | |
| if issues: | |
| return ( | |
| "Improvement plan needed. Focus first on the most visible weaknesses in customer experience " | |
| f"and operations. Main issues: {', '.join(issues[:3])}." | |
| ) | |
| return ( | |
| "Maintain current performance and continue monitoring quality. " | |
| f"Main strengths: {', '.join(strengths[:3]) if strengths else 'overall stable performance'}." | |
| ) | |
| def build_chart(restaurant_name): | |
| subset = business_df[business_df["restaurant_nom"] == restaurant_name].copy() | |
| subset = subset.sort_values("date") | |
| fig, ax = plt.subplots(figsize=(7, 4)) | |
| ax.plot(subset["date"], subset["chiffre_affaire_eur"], marker="o") | |
| ax.set_title(f"Revenue trend - {restaurant_name}") | |
| ax.set_xlabel("Date") | |
| ax.set_ylabel("Revenue (EUR)") | |
| plt.xticks(rotation=45) | |
| plt.tight_layout() | |
| return fig | |
| def analyze_restaurant(restaurant_name): | |
| if not restaurant_name: | |
| return "Please choose a restaurant.", "No data yet.", None | |
| review_rows = summary_df[summary_df["restaurant_nom"] == restaurant_name].copy() | |
| business_rows = business_df[business_df["restaurant_nom"] == restaurant_name].copy() | |
| if review_rows.empty and business_rows.empty: | |
| return f"No data found for {restaurant_name}.", "No data available.", None | |
| review_row = review_rows.iloc[0] if not review_rows.empty else pd.Series(dtype=object) | |
| city = safe_str(review_row.get("ville")) | |
| price_range = safe_str(review_row.get("gamme_prix")) | |
| sanitary = safe_str(review_row.get("niveau_sanitaire_reference")) | |
| nb_reviews = review_row.get("nb_reviews") | |
| avg_review = review_row.get("note_review_moyenne") | |
| pos_share = review_row.get("part_reviews_positives") | |
| mixed_share = review_row.get("part_reviews_mitigees") | |
| neg_share = review_row.get("part_reviews_negatives") | |
| type_restauration = "N/A" | |
| if not business_rows.empty and "type_restauration" in business_rows.columns: | |
| mode_vals = business_rows["type_restauration"].mode() | |
| if not mode_vals.empty: | |
| type_restauration = safe_str(mode_vals.iloc[0]) | |
| avg_google_rating = business_rows["google_rating"].mean() if not business_rows.empty else None | |
| avg_revenue = business_rows["chiffre_affaire_eur"].mean() if not business_rows.empty else None | |
| latest_revenue = None | |
| if not business_rows.empty and business_rows["chiffre_affaire_eur"].notna().any(): | |
| latest_revenue = ( | |
| business_rows.sort_values("date")["chiffre_affaire_eur"].dropna().iloc[-1] | |
| ) | |
| avg_review_text = f"{avg_review:.2f}/5" if pd.notna(avg_review) else "N/A" | |
| avg_google_text = f"{avg_google_rating:.2f}/5" if pd.notna(avg_google_rating) else "N/A" | |
| avg_revenue_text = f"{avg_revenue:,.0f} EUR" if pd.notna(avg_revenue) else "N/A" | |
| latest_revenue_text = f"{latest_revenue:,.0f} EUR" if pd.notna(latest_revenue) else "N/A" | |
| nb_reviews_text = str(int(nb_reviews)) if pd.notna(nb_reviews) else "N/A" | |
| pos_text = f"{pos_share * 100:.1f}%" if pd.notna(pos_share) else "N/A" | |
| mixed_text = f"{mixed_share * 100:.1f}%" if pd.notna(mixed_share) else "N/A" | |
| neg_text = f"{neg_share * 100:.1f}%" if pd.notna(neg_share) else "N/A" | |
| overview = f""" | |
| ## Restaurant Overview | |
| - **Name:** {restaurant_name} | |
| - **City:** {city} | |
| - **Type:** {type_restauration} | |
| - **Price range:** {price_range} | |
| - **Sanitary reference:** {sanitary} | |
| - **Number of reviews:** {nb_reviews_text} | |
| - **Average review score:** {avg_review_text} | |
| - **Average Google rating:** {avg_google_text} | |
| - **Average monthly revenue:** {avg_revenue_text} | |
| - **Latest revenue observed:** {latest_revenue_text} | |
| """ | |
| insight = f""" | |
| ## Review Insight | |
| - **Positive reviews:** {pos_text} | |
| - **Mixed reviews:** {mixed_text} | |
| - **Negative reviews:** {neg_text} | |
| ## Recommendation | |
| {recommendation_text(avg_review, neg_share, sanitary, avg_google_rating)} | |
| """ | |
| fig = build_chart(restaurant_name) if not business_rows.empty else None | |
| return overview, insight, fig | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Restaurant Insight Dashboard") | |
| gr.Markdown( | |
| "Choose a restaurant to view its customer review profile, business indicators, and a simple recommendation." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| restaurant_input = gr.Dropdown( | |
| choices=restaurant_choices, | |
| label="Restaurant name", | |
| value=restaurant_choices[0] if restaurant_choices else None, | |
| ) | |
| analyze_btn = gr.Button("Analyze restaurant") | |
| with gr.Column(scale=2): | |
| overview_output = gr.Markdown() | |
| insight_output = gr.Markdown() | |
| revenue_plot = gr.Plot() | |
| analyze_btn.click( | |
| fn=analyze_restaurant, | |
| inputs=[restaurant_input], | |
| outputs=[overview_output, insight_output, revenue_plot], | |
| ) | |
| demo.load( | |
| fn=analyze_restaurant, | |
| inputs=[restaurant_input], | |
| outputs=[overview_output, insight_output, revenue_plot], | |
| ) | |
| demo.load( | |
| fn=analyze_restaurant, | |
| inputs=[restaurant_input], | |
| outputs=[overview_output, insight_output, revenue_plot], | |
| ) | |
| demo.launch() |