buildersai / app /services /sentiment_service.py
Kushal
Initial deployment: FastAPI backend with Docker
f3997d4
Raw
History Blame Contribute Delete
4.11 kB
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"
@lru_cache()
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