Spaces:
Build error
Build error
Download app.py from bacancydataprophets/Restaurant-Review-Analyzer: direct link, hf CLI and curl.
- Browser
- Download file 9.67 kB
-
https://huggingface.co/spaces/bacancydataprophets/Restaurant-Review-Analyzer/resolve/main/app.py
- Command line
-
hf download hf://spaces/bacancydataprophets/Restaurant-Review-Analyzer/app.py
-
curl -L -o app.py https://huggingface.co/spaces/bacancydataprophets/Restaurant-Review-Analyzer/resolve/main/app.py
9.67 kB
| import streamlit as st | |
| import pandas as pd | |
| from transformers import pipeline | |
| import torch # For GPU checks | |
| import numpy as np | |
| from groq import Client as Groq | |
| import os | |
| from dotenv import load_dotenv | |
| import time | |
| load_dotenv() | |
| os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" | |
| # Set page config | |
| st.set_page_config( | |
| page_title="Restaurant Review Analyzer π½οΈ", | |
| page_icon="π", | |
| layout="wide" | |
| ) | |
| # Custom CSS | |
| st.markdown(""" | |
| <style> | |
| .main { | |
| padding: 2rem; | |
| } | |
| .stProgress > div > div > div { | |
| background-color: #1f77b4; | |
| } | |
| .metric-card { | |
| background-color: #f8f9fa; | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def setup_classifier(): | |
| """Initialize the zero-shot classification pipeline with GPU support if available""" | |
| with st.spinner('Loading classification model... βοΈ'): | |
| device = 0 if torch.cuda.is_available() else -1 # Use GPU if available | |
| return pipeline( | |
| "zero-shot-classification", | |
| model="joeddav/xlm-roberta-large-xnli", | |
| device=device | |
| ) | |
| def create_aspect_labels(): | |
| """Create labels for all aspects with positive/negative sentiment""" | |
| aspects = [ | |
| "food quality", | |
| "service", | |
| "ambiance", | |
| "price", | |
| "cleanliness", | |
| "portion size", | |
| "wait time", | |
| "menu variety" | |
| ] | |
| sentiment_labels = [] | |
| for aspect in aspects: | |
| sentiment_labels.extend([ | |
| f"positive {aspect}", | |
| f"negative {aspect}" | |
| ]) | |
| return aspects, sentiment_labels | |
| def classify_review(classifier, review, sentiment_labels): | |
| """Classify a single review across all aspects and sentiments""" | |
| if pd.isna(review) or not isinstance(review, str): | |
| return {label: 0 for label in sentiment_labels} | |
| try: | |
| result = classifier( | |
| review, | |
| sentiment_labels, | |
| multi_label=True | |
| ) | |
| return dict(zip(result['labels'], result['scores'])) | |
| except Exception as e: | |
| st.error(f"Error processing review: {e}") | |
| return {label: 0 for label in sentiment_labels} | |
| def format_summary_for_llm(summary_df): | |
| """Format the classification summary into a clear text prompt""" | |
| summary_text = "Restaurant Reviews Analysis Summary:\n\n" | |
| sentiment_analysis = {} | |
| for aspect in summary_df.index: | |
| pos = summary_df.loc[aspect, 'positive_mentions'] | |
| neg = summary_df.loc[aspect, 'negative_mentions'] | |
| total = pos + neg | |
| if total > 0: | |
| pos_percent = (pos / total) * 100 | |
| neg_percent = (neg / total) * 100 | |
| difference = pos_percent - neg_percent | |
| sentiment_analysis[aspect] = { | |
| 'difference': difference, | |
| 'positive_percent': pos_percent, | |
| 'negative_percent': neg_percent, | |
| 'total_mentions': total | |
| } | |
| for aspect, metrics in sentiment_analysis.items(): | |
| summary_text += f"{aspect}:\n" | |
| summary_text += f"- Total Mentions: {metrics['total_mentions']}\n" | |
| summary_text += f"- Positive Mentions: {metrics['positive_percent']:.1f}%\n" | |
| summary_text += f"- Negative Mentions: {metrics['negative_percent']:.1f}%\n" | |
| summary_text += f"- Sentiment Difference: {metrics['difference']:.1f}%\n" | |
| summary_text += "\n" | |
| return summary_text | |
| def generate_insights(groq_client, summary_text): | |
| """Generate insights using Groq API""" | |
| prompt = f"""You are an expert restaurant consultant analyzing customer feedback data. | |
| Based on the following customer review analysis summary, provide actionable insights | |
| and recommendations for the restaurant owner. When analyzing the data: | |
| - If an aspect has a positive difference of 0.5% or more, consider it a strength | |
| - If an aspect has a negative difference of 0.5% or more, consider it an area for improvement | |
| - For differences smaller than 0.5%, consider the aspect neutral or mixed | |
| - Pay special attention to aspects with high total mentions as they represent stronger customer sentiment | |
| Analysis Data: | |
| {summary_text} | |
| Please provide: | |
| 1. Key Strengths: What's working well (aspects with >0.5% positive difference) | |
| 2. Areas for Improvement: What needs attention (aspects with >0.5% negative difference) | |
| 3. Mixed Reception Areas: Aspects with minimal difference (<0.5%) between positive and negative | |
| 4. Actionable Recommendations: Specific steps based on the analysis | |
| 5. Priority Actions: What should be addressed first, considering both sentiment differences and total mention count | |
| Format your response in clear sections with bullet points where appropriate. | |
| Add relevant emojis to make the response more engaging. | |
| """ | |
| try: | |
| with st.spinner('Generating insights... π€'): | |
| chat_completion = groq_client.chat.completions.create( | |
| messages=[{"role": "user", "content": prompt}], | |
| model="mixtral-8x7b-32768", | |
| temperature=0.7, | |
| max_tokens=1500, | |
| ) | |
| return chat_completion.choices[0].message.content | |
| except Exception as e: | |
| st.error(f"Error generating insights: {str(e)}") | |
| return None | |
| def main(): | |
| # Header | |
| st.title("π½οΈ Restaurant Review Analyzer") | |
| st.markdown("### Transform your customer feedback into actionable insights! π") | |
| # Sidebar | |
| st.sidebar.header("π Configuration") | |
| # File upload | |
| uploaded_file = st.sidebar.file_uploader("Upload your CSV file", type=['csv']) | |
| if uploaded_file is not None: | |
| # Read CSV | |
| try: | |
| df = pd.read_csv(uploaded_file) | |
| df.columns = df.columns.str.strip() | |
| # Validate 'Review' column | |
| if 'Review' not in df.columns: | |
| st.error("β 'Review' column not found in the CSV file!") | |
| return | |
| # Show sample of uploaded data | |
| st.subheader("π Sample Reviews") | |
| st.dataframe(df[['Review']].head(5), use_container_width=True) | |
| # Process reviews | |
| if st.button("π Analyze Reviews"): | |
| # Initialize classifier | |
| classifier = setup_classifier() | |
| aspects, sentiment_labels = create_aspect_labels() | |
| # Process reviews with progress bar | |
| results = [] | |
| progress_bar = st.progress(0) | |
| status_text = st.empty() | |
| for idx, review in enumerate(df['Review'].head(30)): | |
| status_text.text(f"Processing review {idx + 1}/30...") | |
| scores = classify_review(classifier, review, sentiment_labels) | |
| results.append(scores) | |
| progress_bar.progress((idx + 1) / 30) | |
| results_df = pd.DataFrame(results) | |
| # Analyze results | |
| summary = pd.DataFrame() | |
| for aspect in aspects: | |
| pos_col = f"positive {aspect}" | |
| neg_col = f"negative {aspect}" | |
| summary.loc[aspect, 'positive_mentions'] = (results_df[pos_col] > 0.5).sum() | |
| summary.loc[aspect, 'negative_mentions'] = (results_df[neg_col] > 0.5).sum() | |
| summary.loc[aspect, 'avg_positive_score'] = results_df[pos_col].mean() | |
| summary.loc[aspect, 'avg_negative_score'] = results_df[neg_col].mean() | |
| # Display summary in columns | |
| st.subheader("π Analysis Summary") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("#### π Positive Mentions") | |
| for aspect in aspects: | |
| st.metric( | |
| label=aspect.title(), | |
| value=f"{summary.loc[aspect, 'positive_mentions']} reviews", | |
| delta=f"{summary.loc[aspect, 'avg_positive_score']:.2%} avg. confidence" | |
| ) | |
| with col2: | |
| st.markdown("#### π Negative Mentions") | |
| for aspect in aspects: | |
| st.metric( | |
| label=aspect.title(), | |
| value=f"{summary.loc[aspect, 'negative_mentions']} reviews", | |
| delta=f"{summary.loc[aspect, 'avg_negative_score']:.2%} avg. confidence", | |
| delta_color="inverse" | |
| ) | |
| # Generate insights | |
| groq_client = Groq(api_key=os.getenv("GROQ_API_KEY")) | |
| summary_text = format_summary_for_llm(summary) | |
| insights = generate_insights(groq_client, summary_text) | |
| if insights: | |
| st.subheader("π‘ Key Insights and Recommendations") | |
| st.markdown(insights) | |
| except Exception as e: | |
| st.error(f"Error processing file: {str(e)}") | |
| else: | |
| # Show welcome message and instructions | |
| st.markdown(""" | |
| ### π Welcome to the Restaurant Review Analyzer! | |
| To get started: | |
| 1. π Upload your CSV file containing customer reviews | |
| 2. π Make sure your file has a 'Review' column | |
| 3. π Click 'Analyze Reviews' to process the data | |
| 4. π Get detailed insights and recommendations | |
| The analyzer will process the reviews to provide quick insights! | |
| """) | |
| if __name__ == "__main__": | |
| main() | |