import calendar from collections import defaultdict from django.db.models import Count, Q from apps.surveys.models import SurveyInstance, SurveyResponse CATEGORIES = ["Hospital", "Medical", "Non-Medical", "Nursing", "ER", "Support Services"] SENTIMENTS = ["negative", "positive", "neutral", "gratitude", "suggestion"] class CommentsReportService: def __init__(self, hospital_id, year, quarter): self.hospital_id = hospital_id self.year = year self.quarter = quarter def _base_qs(self): return SurveyInstance.objects.filter( survey_template__hospital_id=self.hospital_id, completed_at__year=self.year, completed_at__month__in=self._quarter_months(), ).exclude(comment="").exclude(comment__isnull=True) def _quarter_months(self): return { 1: [1, 2, 3], 2: [4, 5, 6], 3: [7, 8, 9], 4: [10, 11, 12], }.get(self.quarter, [1, 2, 3]) def get_available_periods(self): months = ( SurveyInstance.objects.filter( survey_template__hospital_id=self.hospital_id, ) .values_list("completed_at__year", "completed_at__month") .distinct() .order_by("-completed_at__year", "-completed_at__month") ) periods = set() for y, m in months: if y is not None and m is not None: q = (m - 1) // 3 + 1 periods.add((y, q)) return sorted(periods, reverse=True) def get_summary(self): qs = self._base_qs() total = qs.count() with_analysis = qs.filter(comment_analyzed=True).count() negative = qs.filter(is_negative=True).count() return { "total": total, "with_analysis": with_analysis, "negative": negative, "positive": total - negative, } def get_category_breakdown(self): qs = self._base_qs().filter(comment_analyzed=True) cat_data = defaultdict(int) for s in qs.iterator(chunk_size=2000): analysis = s.comment_analysis or {} cat = analysis.get("category", "Uncategorized") cat_data[cat] += 1 if not cat_data and qs.count() == 0: return [] result = [{"category": c, "count": cat_data.get(c, 0)} for c in CATEGORIES] return [r for r in result if r["count"] > 0] def get_sentiment_breakdown(self): qs = self._base_qs().filter(comment_analyzed=True) sent_data = defaultdict(int) for s in qs.iterator(chunk_size=2000): analysis = s.comment_analysis or {} sent = analysis.get("sentiment", "neutral") sent_data[sent] += 1 return [{"sentiment": s, "count": sent_data.get(s, 0)} for s in SENTIMENTS if sent_data.get(s, 0) > 0] def get_subcategory_breakdown(self): qs = self._base_qs().filter(comment_analyzed=True) subcat_data = defaultdict(lambda: defaultdict(int)) for s in qs.iterator(chunk_size=2000): analysis = s.comment_analysis or {} cat = analysis.get("category", "Uncategorized") subcat = analysis.get("subcategory", "General") subcat_data[cat][subcat] += 1 result = [] for cat, subcats in sorted(subcat_data.items()): for subcat, count in sorted(subcats.items(), key=lambda x: -x[1]): result.append({ "category": cat, "subcategory": subcat, "count": count, }) return result def get_action_plans(self): qs = self._base_qs().filter(comment_analyzed=True) plans = [] for s in qs.iterator(chunk_size=2000): analysis = s.comment_analysis or {} action = analysis.get("suggested_action_en", "") or analysis.get("suggested_action_ar", "") if action: dept = analysis.get("subcategory", "") plans.append({ "comment": s.comment[:200] if s.comment else "", "action_plan": action, "responsible": dept, "category": analysis.get("category", ""), "status": "Pending", }) return plans[:100] def get_chart_data(self): by_category = self.get_category_breakdown() by_sentiment = self.get_sentiment_breakdown() return { "by_category": [ {"x": c["category"], "y": c["count"]} for c in by_category ], "by_sentiment": [ {"x": s["sentiment"].title(), "y": s["count"]} for s in by_sentiment ], }