| """ |
| Phase 3 User Feedback Collection System |
| Collects and analyzes user feedback for AI-powered chat interface |
| |
| Author: Atom Platform Engineering |
| Date: November 9, 2025 |
| Version: 1.0.0 |
| """ |
|
|
| from datetime import datetime, timedelta |
| from enum import Enum |
| import json |
| from typing import Any, Dict, List, Optional |
| import uuid |
| from fastapi import BackgroundTasks, FastAPI, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel, Field |
| import uvicorn |
|
|
|
|
| class FeedbackType(str, Enum): |
| POSITIVE = "positive" |
| NEGATIVE = "negative" |
| NEUTRAL = "neutral" |
| SUGGESTION = "suggestion" |
| BUG_REPORT = "bug_report" |
| FEATURE_REQUEST = "feature_request" |
|
|
|
|
| class SentimentRating(int, Enum): |
| VERY_NEGATIVE = 1 |
| NEGATIVE = 2 |
| NEUTRAL = 3 |
| POSITIVE = 4 |
| VERY_POSITIVE = 5 |
|
|
|
|
| class UserFeedback(BaseModel): |
| feedback_id: str = Field(default_factory=lambda: str(uuid.uuid4())) |
| user_id: str |
| session_id: Optional[str] = None |
| feedback_type: FeedbackType |
| sentiment_rating: SentimentRating |
| message: str |
| conversation_context: Optional[List[Dict[str, str]]] = None |
| ai_analysis_applied: bool = False |
| ai_sentiment_score: Optional[float] = None |
| ai_intents_detected: Optional[List[str]] = None |
| ai_entities_extracted: Optional[List[Dict[str, str]]] = None |
| response_helpfulness: Optional[int] = Field(None, ge=1, le=5) |
| response_accuracy: Optional[int] = Field(None, ge=1, le=5) |
| response_speed: Optional[int] = Field(None, ge=1, le=5) |
| additional_comments: Optional[str] = None |
| timestamp: str = Field(default_factory=lambda: datetime.now().isoformat()) |
| metadata: Dict[str, Any] = Field(default_factory=dict) |
|
|
|
|
| class FeedbackSummary(BaseModel): |
| total_feedback: int |
| feedback_by_type: Dict[FeedbackType, int] |
| average_sentiment: float |
| average_helpfulness: Optional[float] |
| average_accuracy: Optional[float] |
| average_speed: Optional[float] |
| common_themes: List[str] |
| top_suggestions: List[str] |
| feedback_trend: str |
|
|
|
|
| class FeedbackAnalytics(BaseModel): |
| period_start: str |
| period_end: str |
| total_users: int |
| total_feedback: int |
| feedback_distribution: Dict[FeedbackType, int] |
| sentiment_distribution: Dict[str, int] |
| response_metrics: Dict[str, float] |
| feature_requests: List[str] |
| bug_reports: List[str] |
| user_satisfaction_score: float |
|
|
|
|
| class Phase3FeedbackCollector: |
| def __init__(self): |
| self.feedback_storage: List[UserFeedback] = [] |
| self.analytics_cache: Dict[str, FeedbackAnalytics] = {} |
|
|
| def add_feedback(self, feedback: UserFeedback) -> str: |
| """Add new feedback to storage""" |
| self.feedback_storage.append(feedback) |
|
|
| |
| self.analytics_cache.clear() |
|
|
| return feedback.feedback_id |
|
|
| def get_feedback_by_user(self, user_id: str) -> List[UserFeedback]: |
| """Get all feedback from a specific user""" |
| return [fb for fb in self.feedback_storage if fb.user_id == user_id] |
|
|
| def get_feedback_by_type(self, feedback_type: FeedbackType) -> List[UserFeedback]: |
| """Get all feedback of a specific type""" |
| return [fb for fb in self.feedback_storage if fb.feedback_type == feedback_type] |
|
|
| def get_recent_feedback(self, hours: int = 24) -> List[UserFeedback]: |
| """Get feedback from the last specified hours""" |
| cutoff_time = datetime.now().timestamp() - (hours * 3600) |
| return [ |
| fb |
| for fb in self.feedback_storage |
| if datetime.fromisoformat(fb.timestamp).timestamp() > cutoff_time |
| ] |
|
|
| def calculate_summary(self) -> FeedbackSummary: |
| """Calculate summary statistics for all feedback""" |
| if not self.feedback_storage: |
| return FeedbackSummary( |
| total_feedback=0, |
| feedback_by_type={}, |
| average_sentiment=3.0, |
| average_helpfulness=None, |
| average_accuracy=None, |
| average_speed=None, |
| common_themes=[], |
| top_suggestions=[], |
| feedback_trend="stable", |
| ) |
|
|
| |
| total_feedback = len(self.feedback_storage) |
|
|
| feedback_by_type = {} |
| for fb_type in FeedbackType: |
| feedback_by_type[fb_type] = len(self.get_feedback_by_type(fb_type)) |
|
|
| |
| sentiment_sum = sum(fb.sentiment_rating.value for fb in self.feedback_storage) |
| average_sentiment = sentiment_sum / total_feedback |
|
|
| |
| helpfulness_scores = [ |
| fb.response_helpfulness |
| for fb in self.feedback_storage |
| if fb.response_helpfulness |
| ] |
| accuracy_scores = [ |
| fb.response_accuracy for fb in self.feedback_storage if fb.response_accuracy |
| ] |
| speed_scores = [ |
| fb.response_speed for fb in self.feedback_storage if fb.response_speed |
| ] |
|
|
| average_helpfulness = ( |
| sum(helpfulness_scores) / len(helpfulness_scores) |
| if helpfulness_scores |
| else None |
| ) |
| average_accuracy = ( |
| sum(accuracy_scores) / len(accuracy_scores) if accuracy_scores else None |
| ) |
| average_speed = sum(speed_scores) / len(speed_scores) if speed_scores else None |
|
|
| |
| common_themes = self._extract_common_themes() |
| top_suggestions = self._extract_top_suggestions() |
|
|
| |
| recent_feedback = self.get_recent_feedback(24) |
| if len(recent_feedback) > 5: |
| recent_sentiment = sum( |
| fb.sentiment_rating.value for fb in recent_feedback |
| ) / len(recent_feedback) |
| feedback_trend = ( |
| "improving" if recent_sentiment > average_sentiment else "declining" |
| ) |
| else: |
| feedback_trend = "stable" |
|
|
| return FeedbackSummary( |
| total_feedback=total_feedback, |
| feedback_by_type=feedback_by_type, |
| average_sentiment=average_sentiment, |
| average_helpfulness=average_helpfulness, |
| average_accuracy=average_accuracy, |
| average_speed=average_speed, |
| common_themes=common_themes, |
| top_suggestions=top_suggestions, |
| feedback_trend=feedback_trend, |
| ) |
|
|
| def _extract_common_themes(self) -> List[str]: |
| """Extract common themes from feedback messages""" |
| |
| themes = { |
| "response_quality": [ |
| "slow", |
| "fast", |
| "accurate", |
| "wrong", |
| "correct", |
| "helpful", |
| "unhelpful", |
| ], |
| "ai_features": [ |
| "sentiment", |
| "analysis", |
| "smart", |
| "intelligent", |
| "ai", |
| "understanding", |
| ], |
| "usability": [ |
| "easy", |
| "difficult", |
| "simple", |
| "complex", |
| "intuitive", |
| "confusing", |
| ], |
| "performance": ["slow", "fast", "responsive", "laggy", "quick"], |
| "reliability": ["broken", "working", "reliable", "unreliable", "stable"], |
| } |
|
|
| theme_counts = {theme: 0 for theme in themes.keys()} |
|
|
| for feedback in self.feedback_storage: |
| message_lower = feedback.message.lower() |
| for theme, keywords in themes.items(): |
| if any(keyword in message_lower for keyword in keywords): |
| theme_counts[theme] += 1 |
|
|
| |
| sorted_themes = sorted(theme_counts.items(), key=lambda x: x[1], reverse=True) |
| return [theme for theme, count in sorted_themes[:3] if count > 0] |
|
|
| def _extract_top_suggestions(self) -> List[str]: |
| """Extract top suggestions from feedback""" |
| suggestions = [] |
| for feedback in self.feedback_storage: |
| if feedback.feedback_type == FeedbackType.SUGGESTION: |
| suggestions.append(feedback.message) |
| elif feedback.feedback_type == FeedbackType.FEATURE_REQUEST: |
| suggestions.append(feedback.message) |
|
|
| |
| return suggestions[:5] |
|
|
| def generate_analytics(self, days: int = 7) -> FeedbackAnalytics: |
| """Generate detailed analytics for the specified period""" |
| cache_key = f"analytics_{days}" |
| if cache_key in self.analytics_cache: |
| return self.analytics_cache[cache_key] |
|
|
| cutoff_time = datetime.now() - timedelta(days=days) |
| period_feedback = [ |
| fb |
| for fb in self.feedback_storage |
| if datetime.fromisoformat(fb.timestamp) > cutoff_time |
| ] |
|
|
| if not period_feedback: |
| return FeedbackAnalytics( |
| period_start=cutoff_time.isoformat(), |
| period_end=datetime.now().isoformat(), |
| total_users=0, |
| total_feedback=0, |
| feedback_distribution={}, |
| sentiment_distribution={}, |
| response_metrics={}, |
| feature_requests=[], |
| bug_reports=[], |
| user_satisfaction_score=0.0, |
| ) |
|
|
| |
| total_users = len(set(fb.user_id for fb in period_feedback)) |
| total_feedback = len(period_feedback) |
|
|
| feedback_distribution = {} |
| for fb_type in FeedbackType: |
| count = len([fb for fb in period_feedback if fb.feedback_type == fb_type]) |
| feedback_distribution[fb_type] = count |
|
|
| |
| sentiment_counts = { |
| "very_negative": 0, |
| "negative": 0, |
| "neutral": 0, |
| "positive": 0, |
| "very_positive": 0, |
| } |
|
|
| for fb in period_feedback: |
| if fb.sentiment_rating == SentimentRating.VERY_NEGATIVE: |
| sentiment_counts["very_negative"] += 1 |
| elif fb.sentiment_rating == SentimentRating.NEGATIVE: |
| sentiment_counts["negative"] += 1 |
| elif fb.sentiment_rating == SentimentRating.NEUTRAL: |
| sentiment_counts["neutral"] += 1 |
| elif fb.sentiment_rating == SentimentRating.POSITIVE: |
| sentiment_counts["positive"] += 1 |
| elif fb.sentiment_rating == SentimentRating.VERY_POSITIVE: |
| sentiment_counts["very_positive"] += 1 |
|
|
| |
| helpfulness_scores = [ |
| fb.response_helpfulness for fb in period_feedback if fb.response_helpfulness |
| ] |
| accuracy_scores = [ |
| fb.response_accuracy for fb in period_feedback if fb.response_accuracy |
| ] |
| speed_scores = [ |
| fb.response_speed for fb in period_feedback if fb.response_speed |
| ] |
|
|
| response_metrics = { |
| "average_helpfulness": sum(helpfulness_scores) / len(helpfulness_scores) |
| if helpfulness_scores |
| else 0, |
| "average_accuracy": sum(accuracy_scores) / len(accuracy_scores) |
| if accuracy_scores |
| else 0, |
| "average_speed": sum(speed_scores) / len(speed_scores) |
| if speed_scores |
| else 0, |
| } |
|
|
| |
| feature_requests = [ |
| fb.message |
| for fb in period_feedback |
| if fb.feedback_type == FeedbackType.FEATURE_REQUEST |
| ][:10] |
|
|
| bug_reports = [ |
| fb.message |
| for fb in period_feedback |
| if fb.feedback_type == FeedbackType.BUG_REPORT |
| ][:10] |
|
|
| |
| positive_feedback = len( |
| [fb for fb in period_feedback if fb.sentiment_rating.value >= 4] |
| ) |
| user_satisfaction = ( |
| (positive_feedback / total_feedback) * 100 if total_feedback > 0 else 0 |
| ) |
|
|
| analytics = FeedbackAnalytics( |
| period_start=cutoff_time.isoformat(), |
| period_end=datetime.now().isoformat(), |
| total_users=total_users, |
| total_feedback=total_feedback, |
| feedback_distribution=feedback_distribution, |
| sentiment_distribution=sentiment_counts, |
| response_metrics=response_metrics, |
| feature_requests=feature_requests, |
| bug_reports=bug_reports, |
| user_satisfaction_score=user_satisfaction, |
| ) |
|
|
| |
| self.analytics_cache[cache_key] = analytics |
| return analytics |
|
|
| def export_feedback(self, format_type: str = "json") -> str: |
| """Export feedback data in specified format""" |
| if format_type == "json": |
| return json.dumps([fb.dict() for fb in self.feedback_storage], indent=2) |
| else: |
| raise ValueError(f"Unsupported format: {format_type}") |
|
|
|
|
| |
| app = FastAPI( |
| title="Phase 3 Feedback Collection System", |
| description="Collect and analyze user feedback for AI-powered chat interface", |
| version="1.0.0", |
| ) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| feedback_collector = Phase3FeedbackCollector() |
|
|
|
|
| |
| async def process_feedback_analytics(): |
| """Background task to process feedback analytics""" |
| |
| pass |
|
|
|
|
| |
| @app.post("/api/v1/feedback/submit") |
| async def submit_feedback( |
| feedback: UserFeedback, background_tasks: BackgroundTasks |
| ) -> Dict[str, str]: |
| """Submit user feedback""" |
| try: |
| feedback_id = feedback_collector.add_feedback(feedback) |
| background_tasks.add_task(process_feedback_analytics) |
|
|
| return { |
| "status": "success", |
| "feedback_id": feedback_id, |
| "message": "Feedback submitted successfully", |
| } |
| except Exception as e: |
| raise HTTPException( |
| status_code=500, detail=f"Failed to submit feedback: {str(e)}" |
| ) |
|
|
|
|
| @app.get("/api/v1/feedback/summary") |
| async def get_feedback_summary() -> FeedbackSummary: |
| """Get feedback summary""" |
| return feedback_collector.calculate_summary() |
|
|
|
|
| @app.get("/api/v1/feedback/analytics") |
| async def get_feedback_analytics(days: int = 7) -> FeedbackAnalytics: |
| """Get detailed feedback analytics""" |
| if days not in [1, 7, 30]: |
| raise HTTPException(status_code=400, detail="Days must be 1, 7, or 30") |
|
|
| return feedback_collector.generate_analytics(days) |
|
|
|
|
| @app.get("/api/v1/feedback/user/{user_id}") |
| async def get_user_feedback(user_id: str) -> List[UserFeedback]: |
| """Get all feedback from a specific user""" |
| return feedback_collector.get_feedback_by_user(user_id) |
|
|
|
|
| @app.get("/api/v1/feedback/type/{feedback_type}") |
| async def get_feedback_by_type(feedback_type: FeedbackType) -> List[UserFeedback]: |
| """Get feedback by type""" |
| return feedback_collector.get_feedback_by_type(feedback_type) |
|
|
|
|
| @app.get("/api/v1/feedback/recent") |
| async def get_recent_feedback(hours: int = 24) -> List[UserFeedback]: |
| """Get recent feedback""" |
| if hours > 168: |
| raise HTTPException(status_code=400, detail="Hours cannot exceed 168 (1 week)") |
|
|
| return feedback_collector.get_recent_feedback(hours) |
|
|
|
|
| @app.get("/api/v1/feedback/export") |
| async def export_feedback(format_type: str = "json") -> Dict[str, str]: |
| """Export feedback data""" |
| try: |
| data = feedback_collector.export_feedback(format_type) |
| return {"status": "success", "format": format_type, "data": data} |
| except ValueError as e: |
| raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
| @app.get("/health") |
| async def health_check() -> Dict[str, Any]: |
| """Health check endpoint""" |
| summary = feedback_collector.calculate_summary() |
|
|
| return { |
| "status": "healthy", |
| "version": "1.0.0", |
| "timestamp": datetime.now().isoformat(), |
| "feedback_stats": { |
| "total_feedback": summary.total_feedback, |
| "average_sentiment": summary.average_sentiment, |
| "user_satisfaction": f"{summary.average_sentiment * 20:.1f}%", |
| }, |
| } |
|
|
|
|
| if __name__ == "__main__": |
| uvicorn.run( |
| "phase3_feedback_collection:app", |
| host="0.0.0.0", |
| port=5064, |
| reload=True, |
| log_level="info", |
| ) |
|
|