279 lines
6.5 KiB
Python
279 lines
6.5 KiB
Python
"""
|
|
AI Engine utility functions
|
|
"""
|
|
from typing import Optional
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from .models import SentimentResult
|
|
|
|
|
|
def get_sentiment_badge_class(sentiment: str) -> str:
|
|
"""
|
|
Get Bootstrap badge class for sentiment.
|
|
|
|
Args:
|
|
sentiment: Sentiment value ('positive', 'neutral', 'negative')
|
|
|
|
Returns:
|
|
Bootstrap badge class
|
|
"""
|
|
badge_classes = {
|
|
'positive': 'bg-success',
|
|
'neutral': 'bg-secondary',
|
|
'negative': 'bg-danger',
|
|
}
|
|
return badge_classes.get(sentiment, 'bg-secondary')
|
|
|
|
|
|
def get_sentiment_icon(sentiment: str) -> str:
|
|
"""
|
|
Get icon for sentiment.
|
|
|
|
Args:
|
|
sentiment: Sentiment value ('positive', 'neutral', 'negative')
|
|
|
|
Returns:
|
|
Icon class or emoji
|
|
"""
|
|
icons = {
|
|
'positive': '😊',
|
|
'neutral': '😐',
|
|
'negative': '😞',
|
|
}
|
|
return icons.get(sentiment, '😐')
|
|
|
|
|
|
def format_sentiment_score(score: float) -> str:
|
|
"""
|
|
Format sentiment score for display.
|
|
|
|
Args:
|
|
score: Sentiment score (-1 to 1)
|
|
|
|
Returns:
|
|
Formatted score string
|
|
"""
|
|
return f"{score:+.2f}"
|
|
|
|
|
|
def format_confidence(confidence: float) -> str:
|
|
"""
|
|
Format confidence as percentage.
|
|
|
|
Args:
|
|
confidence: Confidence value (0 to 1)
|
|
|
|
Returns:
|
|
Formatted percentage string
|
|
"""
|
|
return f"{confidence * 100:.1f}%"
|
|
|
|
|
|
def get_sentiment_color(sentiment: str) -> str:
|
|
"""
|
|
Get color code for sentiment.
|
|
|
|
Args:
|
|
sentiment: Sentiment value ('positive', 'neutral', 'negative')
|
|
|
|
Returns:
|
|
Hex color code
|
|
"""
|
|
colors = {
|
|
'positive': '#28a745', # Green
|
|
'neutral': '#6c757d', # Gray
|
|
'negative': '#dc3545', # Red
|
|
}
|
|
return colors.get(sentiment, '#6c757d')
|
|
|
|
|
|
def get_emotion_icon(emotion: str) -> str:
|
|
"""
|
|
Get icon/emoji for emotion.
|
|
|
|
Args:
|
|
emotion: Emotion name
|
|
|
|
Returns:
|
|
Emoji representing the emotion
|
|
"""
|
|
icons = {
|
|
'joy': '😄',
|
|
'anger': '😠',
|
|
'sadness': '😢',
|
|
'fear': '😨',
|
|
'surprise': '😲',
|
|
'disgust': '🤢',
|
|
'trust': '🤝',
|
|
'anticipation': '🤔',
|
|
}
|
|
return icons.get(emotion, '😐')
|
|
|
|
|
|
def has_sentiment_analysis(obj) -> bool:
|
|
"""
|
|
Check if an object has sentiment analysis.
|
|
|
|
Args:
|
|
obj: Django model instance
|
|
|
|
Returns:
|
|
True if sentiment analysis exists
|
|
"""
|
|
content_type = ContentType.objects.get_for_model(obj)
|
|
return SentimentResult.objects.filter(
|
|
content_type=content_type,
|
|
object_id=obj.id
|
|
).exists()
|
|
|
|
|
|
def get_latest_sentiment(obj) -> Optional[SentimentResult]:
|
|
"""
|
|
Get the latest sentiment analysis for an object.
|
|
|
|
Args:
|
|
obj: Django model instance
|
|
|
|
Returns:
|
|
SentimentResult instance or None
|
|
"""
|
|
content_type = ContentType.objects.get_for_model(obj)
|
|
return SentimentResult.objects.filter(
|
|
content_type=content_type,
|
|
object_id=obj.id
|
|
).first()
|
|
|
|
|
|
def get_sentiment_summary(sentiment_score: float) -> str:
|
|
"""
|
|
Get human-readable sentiment summary.
|
|
|
|
Args:
|
|
sentiment_score: Sentiment score (-1 to 1)
|
|
|
|
Returns:
|
|
Human-readable summary
|
|
"""
|
|
if sentiment_score >= 0.6:
|
|
return "Very Positive"
|
|
elif sentiment_score >= 0.2:
|
|
return "Positive"
|
|
elif sentiment_score >= -0.2:
|
|
return "Neutral"
|
|
elif sentiment_score >= -0.6:
|
|
return "Negative"
|
|
else:
|
|
return "Very Negative"
|
|
|
|
|
|
def calculate_sentiment_trend(results: list) -> dict:
|
|
"""
|
|
Calculate sentiment trend from a list of results.
|
|
|
|
Args:
|
|
results: List of SentimentResult instances
|
|
|
|
Returns:
|
|
Dictionary with trend information
|
|
"""
|
|
if not results:
|
|
return {
|
|
'direction': 'stable',
|
|
'change': 0,
|
|
'description': 'No data'
|
|
}
|
|
|
|
# Calculate average scores for first and second half
|
|
mid_point = len(results) // 2
|
|
first_half = results[:mid_point]
|
|
second_half = results[mid_point:]
|
|
|
|
if not first_half or not second_half:
|
|
return {
|
|
'direction': 'stable',
|
|
'change': 0,
|
|
'description': 'Insufficient data'
|
|
}
|
|
|
|
first_avg = sum(float(r.sentiment_score) for r in first_half) / len(first_half)
|
|
second_avg = sum(float(r.sentiment_score) for r in second_half) / len(second_half)
|
|
|
|
change = second_avg - first_avg
|
|
|
|
if change > 0.1:
|
|
direction = 'improving'
|
|
description = 'Sentiment is improving'
|
|
elif change < -0.1:
|
|
direction = 'declining'
|
|
description = 'Sentiment is declining'
|
|
else:
|
|
direction = 'stable'
|
|
description = 'Sentiment is stable'
|
|
|
|
return {
|
|
'direction': direction,
|
|
'change': change,
|
|
'description': description
|
|
}
|
|
|
|
|
|
def get_top_keywords(results: list, limit: int = 10) -> list:
|
|
"""
|
|
Get top keywords from a list of sentiment results.
|
|
|
|
Args:
|
|
results: List of SentimentResult instances
|
|
limit: Maximum number of keywords to return
|
|
|
|
Returns:
|
|
List of (keyword, count) tuples
|
|
"""
|
|
from collections import Counter
|
|
|
|
all_keywords = []
|
|
for result in results:
|
|
all_keywords.extend(result.keywords)
|
|
|
|
keyword_counts = Counter(all_keywords)
|
|
return keyword_counts.most_common(limit)
|
|
|
|
|
|
def get_sentiment_distribution(results: list) -> dict:
|
|
"""
|
|
Get sentiment distribution from a list of results.
|
|
|
|
Args:
|
|
results: List of SentimentResult instances
|
|
|
|
Returns:
|
|
Dictionary with sentiment counts and percentages
|
|
"""
|
|
total = len(results)
|
|
|
|
if total == 0:
|
|
return {
|
|
'positive': {'count': 0, 'percentage': 0},
|
|
'neutral': {'count': 0, 'percentage': 0},
|
|
'negative': {'count': 0, 'percentage': 0},
|
|
}
|
|
|
|
positive = sum(1 for r in results if r.sentiment == 'positive')
|
|
neutral = sum(1 for r in results if r.sentiment == 'neutral')
|
|
negative = sum(1 for r in results if r.sentiment == 'negative')
|
|
|
|
return {
|
|
'positive': {
|
|
'count': positive,
|
|
'percentage': round((positive / total) * 100, 1)
|
|
},
|
|
'neutral': {
|
|
'count': neutral,
|
|
'percentage': round((neutral / total) * 100, 1)
|
|
},
|
|
'negative': {
|
|
'count': negative,
|
|
'percentage': round((negative / total) * 100, 1)
|
|
},
|
|
}
|