uchill/chatnext/backend/apps/chatbot/models/message_feedback.py

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,
}