r-vasanthkumar73-dev's picture
Deploying backend and frontend folder modules.
099d157 verified
Raw
History Blame Contribute Delete
8.21 kB
"""
Text Sentiment & Emotion — High Precision Emotion Engine
Uses j-hartmann/emotion-english-distilroberta-base (7-class).
Verified labels: anger, disgust, fear, joy, neutral, sadness, surprise
"""
_classifier = None
_mode = None
def get_classifier():
global _classifier, _mode
if _classifier is not None:
return _classifier
try:
from transformers import pipeline as hf_pipeline
import torch
device = 0 if torch.cuda.is_available() else -1
_classifier = hf_pipeline(
"text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
top_k=None,
device=device
)
_mode = "transformers"
print("[TextModel] HuggingFace precise emotion engine loaded: emotion-english-distilroberta-base (7-class)")
except Exception as e:
print(f"[TextModel] HuggingFace unavailable ({e}), using keyword heuristic")
_classifier = "HEURISTIC"
_mode = "heuristic"
return _classifier
def analyze_text(text):
if not text:
return _empty_result()
word_count = len(text.split())
classifier = get_classifier()
if _mode == "transformers" and classifier != "HEURISTIC":
try:
results = classifier(text[:1500])
# top_k=None returns a list of dicts directly
if isinstance(results[0], list):
results = results[0]
emotions = {res["label"]: res["score"] for res in results}
# Find dominant emotion
dominant = max(emotions, key=emotions.get)
score = emotions[dominant]
# ── Negation Neutralizer ──────────────────────────────────────
# If the model outputs a strong NEGATIVE emotion but the sentence
# contains clear negation words before negative content, downgrade to neutral.
dominant, score, emotions = _apply_negation_check(text, dominant, score, emotions)
# ─────────────────────────────────────────────────────────────
return _format_emotion_output(dominant, score, emotions, word_count, "DistilRoBERTa-Emotion")
except Exception as e:
print(f"Text Model Error: {e}")
import traceback
traceback.print_exc()
return _keyword_classify(text, word_count)
else:
return _keyword_classify(text, word_count)
def _apply_negation_check(text, dominant, score, emotions):
"""
Post-process the ML result to detect negated negative phrases.
If the model said NEGATIVE but the sentence is clearly negating that negativity
(e.g. 'not going to hell', 'don't hate'), override to Neutral.
Also handles mixed sentences (happy + sad) by checking balance.
"""
text_lower = text.lower()
words = text_lower.split()
# Negation words that flip meaning
negation_words = {"not", "no", "never", "don't", "dont", "won't", "wont",
"can't", "cant", "isn't", "isnt", "aren't", "arent",
"wouldn't", "wouldnt", "shouldn't", "shouldnt", "didn't", "didnt"}
# Strong negative content words (things that sound bad but may be negated)
strong_negatives = {"hell", "die", "death", "dead", "hate", "terrible",
"awful", "horrible", "evil", "hurt", "kill", "murder",
"suffer", "pain", "miserable", "disaster", "fail", "failure"}
negative_emotions = {"anger", "disgust", "fear", "sadness"}
if dominant in negative_emotions:
# Check: is there a negation word within 3 words before a strong negative?
for i, w in enumerate(words):
if w in negation_words:
window = words[i+1 : i+4] # next 3 words after negation
if any(neg in window for neg in strong_negatives):
# Negated negative → override to neutral
neutral_score = min(score * 0.8, 0.75)
emotions["neutral"] = neutral_score
return "neutral", neutral_score, emotions
# Mixed sentence check: contains both strong positive AND negative content
positive_words = {"happy", "love", "great", "good", "joy", "excited",
"wonderful", "amazing", "glad", "pleased", "enjoy"}
negative_words = {"sad", "angry", "hate", "fear", "bad", "terrible",
"upset", "awful", "hurt", "cry", "depressed"}
has_positive = any(w in words for w in positive_words)
has_negative = any(w in words for w in negative_words)
if has_positive and has_negative and dominant in negative_emotions:
# Mixed sentence — reduce confidence and lean toward neutral
neutral_score = score * 0.6
emotions["neutral"] = neutral_score
if neutral_score > score * 0.55:
return "neutral", neutral_score, emotions
return dominant, score, emotions
def _format_emotion_output(dominant, score, all_emotions, word_count, provider):
# Engagement scores that LOGICALLY match each emotion
# High engagement = joy/surprise. Low = sadness/fear/disgust/anger. Medium = neutral
engagement_map = {
"joy": round(65 + (score * 34)), # 65-99% — very engaged
"surprise": round(55 + (score * 30)), # 55-85% — alert/engaged
"neutral": round(35 + (score * 20)), # 35-55% — baseline
"sadness": round(5 + (score * 20)), # 5-25% — disengaged/distressed
"anger": round(10 + (score * 25)), # 10-35% — agitated but not engaged
"fear": round(10 + (score * 20)), # 10-30% — withdrawn
"disgust": round(5 + (score * 15)), # 5-20% — very disengaged
}
eng = engagement_map.get(dominant, 40)
# Sentiment polarity
positive_emotions = ["joy", "surprise"]
negative_emotions = ["anger", "disgust", "fear", "sadness"]
# sentiment_score: represent the actual MODEL CONFIDENCE (0.0 to 1.0)
# displayed in the UI as a percentage of how strongly this emotion was detected
if dominant in positive_emotions:
sentiment = "POSITIVE"
sentiment_score = round(score, 2) # e.g. 0.97 → displayed as 97%
elif dominant in negative_emotions:
sentiment = "NEGATIVE"
sentiment_score = round(score, 2) # e.g. 0.91 → displayed as 91%
else:
sentiment = "NEUTRAL"
sentiment_score = round(score, 2)
# Format percentages for UI log
formatted_emotions = {k: round(v * 100) for k, v in all_emotions.items() if v > 0.01}
return {
"sentiment": sentiment,
"sentiment_score": sentiment_score,
"emotions": formatted_emotions,
"engagement_score": eng,
"provider": provider,
"word_count": word_count,
"subjectivity": round(score, 2),
"dominant_emotion": dominant.capitalize()
}
def _empty_result():
return {
"sentiment": "NEUTRAL",
"sentiment_score": 0.5,
"emotions": {},
"engagement_score": 50,
"word_count": 0,
"subjectivity": 0.0,
"provider": "None"
}
def batch_analyze(texts):
return [analyze_text(t) for t in texts]
def _keyword_classify(text, word_count):
"""Fast keyword-based precise fallback."""
text_lower = text.lower()
positive = ["good", "great", "excellent", "happy", "love", "awesome"]
negative = ["bad", "terrible", "awful", "hate", "angry", "sad"]
pos = sum(1 for w in positive if w in text_lower)
neg = sum(1 for w in negative if w in text_lower)
if pos > neg:
return _format_emotion_output("joy", min(0.6 + pos*0.1, 0.99), {"joy":0.8, "neutral":0.2}, word_count, "Heuristic")
elif neg > pos:
return _format_emotion_output("sadness", min(0.6 + neg*0.1, 0.99), {"sadness":0.8, "neutral":0.2}, word_count, "Heuristic")
else:
return _format_emotion_output("neutral", 0.7, {"neutral": 0.8}, word_count, "Heuristic")