230 lines
6.9 KiB
Python
230 lines
6.9 KiB
Python
"""
|
|
Message Feedback Model - User ratings and feedback on AI responses.
|
|
"""
|
|
|
|
from django.db import models
|
|
from django.conf import settings
|
|
from django.utils.translation import gettext_lazy as _
|
|
from core.models import TimestampedModel
|
|
|
|
|
|
class MessageFeedback(TimestampedModel):
|
|
"""
|
|
Store user feedback on AI-generated messages.
|
|
|
|
This helps with:
|
|
- Quality monitoring
|
|
- Model fine-tuning
|
|
- User satisfaction tracking
|
|
- Issue identification
|
|
"""
|
|
|
|
# User and session
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name="message_feedback",
|
|
help_text=_("User who provided feedback"),
|
|
)
|
|
|
|
chat_session = models.ForeignKey(
|
|
"chatbot.ChatSession",
|
|
on_delete=models.CASCADE,
|
|
related_name="message_feedback",
|
|
help_text=_("Chat session this feedback belongs to"),
|
|
)
|
|
|
|
# Message identification from LangGraph checkpoint
|
|
checkpoint_id = models.CharField(
|
|
max_length=255, help_text=_("LangGraph checkpoint ID containing the message")
|
|
)
|
|
|
|
message_index = models.IntegerField(
|
|
help_text=_("Index of the message in the checkpoint")
|
|
)
|
|
|
|
# Rating
|
|
rating = models.CharField(
|
|
max_length=20,
|
|
choices=[
|
|
("thumbs_up", "Thumbs Up 👍"),
|
|
("thumbs_down", "Thumbs Down 👎"),
|
|
("excellent", "Excellent"),
|
|
("good", "Good"),
|
|
("neutral", "Neutral"),
|
|
("poor", "Poor"),
|
|
("very_poor", "Very Poor"),
|
|
],
|
|
help_text=_("User rating for the message"),
|
|
)
|
|
|
|
# Feedback categories
|
|
feedback_categories = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text=_(
|
|
'Categories of feedback (e.g., ["incorrect", "helpful", "creative"])'
|
|
),
|
|
)
|
|
|
|
# Text feedback
|
|
feedback_text = models.TextField(
|
|
blank=True, null=True, help_text=_("Optional detailed feedback from user")
|
|
)
|
|
|
|
# Issue tracking
|
|
reported_issue = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
choices=[
|
|
("incorrect", "Incorrect Information"),
|
|
("harmful", "Harmful Content"),
|
|
("biased", "Biased Response"),
|
|
("off_topic", "Off Topic"),
|
|
("incomplete", "Incomplete Answer"),
|
|
("technical_error", "Technical Error"),
|
|
("other", "Other"),
|
|
],
|
|
help_text=_("Type of issue if reporting a problem"),
|
|
)
|
|
|
|
# Context preservation
|
|
message_preview = models.TextField(
|
|
blank=True, null=True, help_text=_("Preview of the message (for admin review)")
|
|
)
|
|
|
|
model_used = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text=_("AI model that generated the message"),
|
|
)
|
|
|
|
# Admin review
|
|
reviewed = models.BooleanField(
|
|
default=False, help_text=_("Whether admin has reviewed this feedback")
|
|
)
|
|
|
|
reviewed_at = models.DateTimeField(
|
|
null=True, blank=True, help_text=_("When this feedback was reviewed")
|
|
)
|
|
|
|
reviewed_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name="reviewed_feedback",
|
|
help_text=_("Admin who reviewed this feedback"),
|
|
)
|
|
|
|
admin_notes = models.TextField(
|
|
blank=True, null=True, help_text=_("Internal notes from admin review")
|
|
)
|
|
|
|
# Action taken
|
|
action_taken = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
choices=[
|
|
("none", "No Action"),
|
|
("noted", "Noted for Training"),
|
|
("fixed", "Issue Fixed"),
|
|
("escalated", "Escalated"),
|
|
("user_notified", "User Notified"),
|
|
],
|
|
help_text=_("Action taken based on this feedback"),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Message Feedback")
|
|
verbose_name_plural = _("Message Feedback")
|
|
ordering = ["-created_at"]
|
|
indexes = [
|
|
models.Index(
|
|
fields=["user", "-created_at"], name="msgfeedback_user_date_idx"
|
|
),
|
|
models.Index(
|
|
fields=["chat_session", "-created_at"],
|
|
name="msgfeedback_session_date_idx",
|
|
),
|
|
models.Index(fields=["rating"], name="msgfeedback_rating_idx"),
|
|
models.Index(fields=["reported_issue"], name="msgfeedback_issue_idx"),
|
|
models.Index(fields=["reviewed"], name="msgfeedback_reviewed_idx"),
|
|
]
|
|
unique_together = ["checkpoint_id", "message_index", "user"]
|
|
|
|
def __str__(self):
|
|
return f"{self.user.email} - {self.rating} - Session {self.chat_session.title}"
|
|
|
|
def mark_reviewed(self, reviewer, action_taken="noted", admin_notes=""):
|
|
"""Mark feedback as reviewed by admin."""
|
|
from django.utils import timezone
|
|
|
|
self.reviewed = True
|
|
self.reviewed_at = timezone.now()
|
|
self.reviewed_by = reviewer
|
|
self.action_taken = action_taken
|
|
if admin_notes:
|
|
self.admin_notes = admin_notes
|
|
|
|
self.save(
|
|
update_fields=[
|
|
"reviewed",
|
|
"reviewed_at",
|
|
"reviewed_by",
|
|
"action_taken",
|
|
"admin_notes",
|
|
]
|
|
)
|
|
|
|
@classmethod
|
|
def get_session_satisfaction(cls, chat_session):
|
|
"""
|
|
Get satisfaction metrics for a session.
|
|
|
|
Returns:
|
|
dict: Satisfaction stats
|
|
"""
|
|
feedback = cls.objects.filter(chat_session=chat_session)
|
|
|
|
total = feedback.count()
|
|
if total == 0:
|
|
return {"total": 0, "satisfaction_rate": 0.0}
|
|
|
|
positive = feedback.filter(
|
|
rating__in=["thumbs_up", "excellent", "good"]
|
|
).count()
|
|
negative = feedback.filter(
|
|
rating__in=["thumbs_down", "poor", "very_poor"]
|
|
).count()
|
|
|
|
return {
|
|
"total": total,
|
|
"positive": positive,
|
|
"negative": negative,
|
|
"neutral": total - positive - negative,
|
|
"satisfaction_rate": (positive / total * 100) if total > 0 else 0.0,
|
|
}
|
|
|
|
@classmethod
|
|
def get_user_satisfaction(cls, user):
|
|
"""Get overall satisfaction for user's conversations."""
|
|
feedback = cls.objects.filter(user=user)
|
|
|
|
total = feedback.count()
|
|
if total == 0:
|
|
return {"total": 0, "satisfaction_rate": 0.0}
|
|
|
|
positive = feedback.filter(
|
|
rating__in=["thumbs_up", "excellent", "good"]
|
|
).count()
|
|
|
|
return {
|
|
"total": total,
|
|
"positive": positive,
|
|
"satisfaction_rate": (positive / total * 100) if total > 0 else 0.0,
|
|
}
|