Spaces:
Runtime error
Runtime error
| from transformers import AutoModelForSequenceClassification, AutoTokenizer, AutoConfig | |
| import numpy as np | |
| from scipy.special import softmax | |
| from functools import lru_cache | |
| # Use a better sentiment model trained on Twitter/news (not movie reviews!) | |
| MODEL = "cardiffnlp/twitter-roberta-base-sentiment-latest" | |
| def load_sentiment_model(): | |
| """Load and cache the sentiment model and tokenizer.""" | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL) | |
| config = AutoConfig.from_pretrained(MODEL) | |
| model = AutoModelForSequenceClassification.from_pretrained(MODEL) | |
| return tokenizer, config, model | |
| def analyze_sentiment(text: str) -> str: | |
| """ | |
| Analyze sentiment using Twitter-RoBERTa (better for news/social media). | |
| Args: | |
| text: Text to analyze | |
| Returns: | |
| Sentiment classification: 'Positive', 'Negative', or 'Neutral' | |
| """ | |
| if not text or not text.strip(): | |
| return "Neutral" | |
| try: | |
| tokenizer, config, model = load_sentiment_model() | |
| # Tokenize and get prediction | |
| encoded_input = tokenizer(text[:512], return_tensors='pt', truncation=True, max_length=512) | |
| output = model(**encoded_input) | |
| scores = output[0][0].detach().numpy() | |
| scores = softmax(scores) | |
| # Get label with highest score | |
| # labels: ['negative', 'neutral', 'positive'] | |
| ranking = np.argsort(scores)[::-1] | |
| label_index = ranking[0] | |
| confidence = scores[label_index] | |
| # Map to our format | |
| labels = ['Negative', 'Neutral', 'Positive'] | |
| result = labels[label_index] | |
| # Only return Positive/Negative if confidence > 50% | |
| # Otherwise return Neutral | |
| if confidence > 0.5: | |
| return result | |
| else: | |
| return "Neutral" | |
| except Exception as e: | |
| print(f"❌ Sentiment analysis failed: {e}") | |
| # Fallback to keyword-based | |
| return _keyword_sentiment(text) | |
| def _keyword_sentiment(text: str) -> str: | |
| """Fallback keyword-based sentiment for construction/legal news.""" | |
| text_lower = text.lower() | |
| # Negative keywords for construction/legal news | |
| negative_words = [ | |
| 'illegal', 'scam', 'fraud', 'violation', 'criticise', 'criticize', | |
| 'fine', 'penalty', 'halted', 'stopped', 'delay', 'problem', 'issue', | |
| 'allege', 'complaint', 'reject', 'denied', 'unsafe', 'danger', | |
| 'cost overrun', 'budget exceed', 'dispute', 'litigation', 'demolition', | |
| 'encroachment', 'unauthorised', 'unauthorized', 'fail', 'failed' | |
| ] | |
| # Positive keywords | |
| positive_words = [ | |
| 'approve', 'approved', 'success', 'complete', 'completed', 'inaugurate', | |
| 'new project', 'development', 'growth', 'expansion', 'modern', 'upgrade', | |
| 'improvement', 'benefit', 'efficient', 'green', 'sustainable', 'award', | |
| 'milestone', 'breakthrough', 'innovation', 'reduce penalty' | |
| ] | |
| neg_count = sum(1 for word in negative_words if word in text_lower) | |
| pos_count = sum(1 for word in positive_words if word in text_lower) | |
| if neg_count > pos_count and neg_count > 0: | |
| return "Negative" | |
| elif pos_count > neg_count and pos_count > 0: | |
| return "Positive" | |
| else: | |
| return "Neutral" | |
| def get_sentiment_score(text: str) -> float: | |
| """ | |
| Get numerical sentiment score. | |
| Args: | |
| text: Text to analyze | |
| Returns: | |
| Score between -1 (negative) and 1 (positive) | |
| """ | |
| if not text or not text.strip(): | |
| return 0.0 | |
| try: | |
| tokenizer, config, model = load_sentiment_model() | |
| encoded_input = tokenizer(text[:512], return_tensors='pt', truncation=True, max_length=512) | |
| output = model(**encoded_input) | |
| scores = output[0][0].detach().numpy() | |
| scores = softmax(scores) | |
| # Convert to -1 to 1 scale | |
| # scores[0] = negative, scores[1] = neutral, scores[2] = positive | |
| return float(scores[2] - scores[0]) | |
| except Exception: | |
| return 0.0 | |