Oliver-ai-bot_2.0/backend/app/models/feedback.py
Vadym Samoilenko f9172a1fea feat: user feedback system for RAG responses
- MessageFeedback model + migration 020 (per-message thumbs up/down,
  with user comment, status workflow, and source snapshot)
- Feedback API endpoints: submit/upsert, batch conversation load,
  admin list/stats/export (CSV + JSONL for model training)
- Access control: super_admin + content_manager
- Frontend: thumbs up/down on assistant messages after streaming,
  optional comment on negative, RAG helpfulness footer with email
- Admin FeedbackTab: stats cards, 30-day trend chart, knowledge gap
  list, filterable table, context dialog, mark reviewed/resolved
- Analytics overview extended with feedback summary fields
- SSE done event now captured to link frontend messages to DB IDs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:59:25 +01:00

39 lines
1.9 KiB
Python

"""
MessageFeedback model — user ratings on assistant responses
"""
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, UniqueConstraint, Index
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
import uuid
from datetime import datetime
class MessageFeedback(Base):
__tablename__ = "message_feedback"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
message_id = Column(UUID(as_uuid=True), ForeignKey("messages.id", ondelete="CASCADE"), nullable=False)
conversation_id = Column(UUID(as_uuid=True), ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
rating = Column(String(20), nullable=False) # 'positive' or 'negative'
user_comment = Column(Text, nullable=True)
retrieved_sources = Column(Text, nullable=True) # JSON-serialized Source[] snapshot
status = Column(String(20), nullable=False, default="pending") # pending/reviewed/resolved
reviewed_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
reviewed_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", foreign_keys=[user_id])
reviewer = relationship("User", foreign_keys=[reviewed_by])
__table_args__ = (
UniqueConstraint("message_id", "user_id", name="uq_feedback_message_user"),
Index("ix_message_feedback_message_id", "message_id"),
Index("ix_message_feedback_conversation_id", "conversation_id"),
Index("ix_message_feedback_user_id", "user_id"),
Index("ix_message_feedback_status", "status"),
Index("ix_message_feedback_rating", "rating"),
Index("ix_message_feedback_created_at", "created_at"),
)