""" Executive Summary Celery Tasks Scheduled tasks for: - Generating weekly/monthly executive reports - Calculating daily metrics - Generating predictive insights - Creating AI recommendations - Sending PDF reports via email """ from celery import shared_task from django.utils import timezone from django.core.files.base import ContentFile from celery import shared_task logger = logging.getLogger(__name__) @shared_task def generate_weekly_executive_summary(): """ Generate weekly executive summary report. Runs every Monday at 6 AM. Creates an ExecutiveReport with AI-generated narrative for the past week. """ from .models import ExecutiveReport from .services import AINarrativeService try: logger.info("Starting weekly executive summary generation") end_date = timezone.now().date() start_date = end_date - timedelta(days=7) # Check if report already exists existing = ExecutiveReport.objects.filter( report_type="weekly", start_date=start_date, end_date=end_date, ).first() if existing: logger.info(f"Weekly report already exists for {start_date} to {end_date}") return {"status": "skipped", "report_id": str(existing.id)} # Generate narratives using AI narrative_service = AINarrativeService() # Generate English narrative logger.info("Generating English narrative...") en_result = narrative_service.generate_weekly_narrative( start_date=start_date, end_date=end_date, ) # Generate Arabic narrative logger.info("Generating Arabic narrative...") ar_result = narrative_service.generate_arabic_narrative( start_date=start_date, end_date=end_date, report_type="weekly", ) # Create report report = ExecutiveReport.objects.create( report_type="weekly", status=en_result.get("status", "completed"), start_date=start_date, end_date=end_date, narrative_en=en_result.get("narrative_en", ""), narrative_ar=ar_result.get("narrative_ar", ""), highlights_en=en_result.get("highlights_en", []), highlights_ar=ar_result.get("highlights_ar", []), concerns_en=en_result.get("concerns_en", []), concerns_ar=ar_result.get("concerns_ar", []), ai_model=en_result.get("ai_model", ""), generation_time_ms=en_result.get("generation_time_ms"), error_message=en_result.get("error", "") or ar_result.get("error", ""), ) # Generate and save PDF try: from .pdf_service import generate_executive_pdf pdf_bytes = generate_executive_pdf(report) filename = f"executive_report_{report.id}_{report.start_date}.pdf" report.pdf_file.save(filename, ContentFile(pdf_bytes), save=True) logger.info(f"PDF saved for report {report.id}") except Exception as pdf_err: logger.error(f"Failed to generate PDF for report {report.id}: {pdf_err}", exc_info=True) logger.info(f"Weekly executive summary generated: {report.id}") return { "status": "success", "report_id": str(report.id), "report_type": "weekly", } except Exception as e: logger.error(f"Failed to generate weekly executive summary: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)} @shared_task def generate_monthly_executive_summary(): """ Generate monthly executive summary report. Runs on 1st of each month at 7 AM. Creates an ExecutiveReport with AI-generated narrative for the past month. """ from .models import ExecutiveReport from .services import AINarrativeService try: logger.info("Starting monthly executive summary generation") end_date = timezone.now().date() start_date = end_date - timedelta(days=30) # Check if report already exists existing = ExecutiveReport.objects.filter( report_type="monthly", start_date=start_date, end_date=end_date, ).first() if existing: logger.info(f"Monthly report already exists for {start_date} to {end_date}") return {"status": "skipped", "report_id": str(existing.id)} # Generate narratives using AI narrative_service = AINarrativeService() # Generate English narrative logger.info("Generating English narrative...") en_result = narrative_service.generate_monthly_narrative( start_date=start_date, end_date=end_date, ) # Generate Arabic narrative logger.info("Generating Arabic narrative...") ar_result = narrative_service.generate_arabic_narrative( start_date=start_date, end_date=end_date, report_type="monthly", ) # Create report report = ExecutiveReport.objects.create( report_type="monthly", status=en_result.get("status", "completed"), start_date=start_date, end_date=end_date, narrative_en=en_result.get("narrative_en", ""), narrative_ar=ar_result.get("narrative_ar", ""), highlights_en=en_result.get("highlights_en", []), highlights_ar=ar_result.get("highlights_ar", []), concerns_en=en_result.get("concerns_en", []), concerns_ar=ar_result.get("concerns_ar", []), ai_model=en_result.get("ai_model", ""), generation_time_ms=en_result.get("generation_time_ms"), error_message=en_result.get("error", "") or ar_result.get("error", ""), ) # Generate and save PDF try: from .pdf_service import generate_executive_pdf pdf_bytes = generate_executive_pdf(report) filename = f"executive_report_{report.id}_{report.start_date}.pdf" report.pdf_file.save(filename, ContentFile(pdf_bytes), save=True) logger.info(f"PDF saved for report {report.id}") except Exception as pdf_err: logger.error(f"Failed to generate PDF for report {report.id}: {pdf_err}", exc_info=True) logger.info(f"Monthly executive summary generated: {report.id}") return { "status": "success", "report_id": str(report.id), "report_type": "monthly", } except Exception as e: logger.error(f"Failed to generate monthly executive summary: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)} @shared_task def calculate_daily_metrics(): """ Calculate and store daily metrics for all hospitals. Runs daily at 1 AM. Aggregates metrics from complaints, surveys, actions, observations, etc. """ from decimal import Decimal from .models import ExecutiveMetric from .services import ExecutiveSummaryService from apps.organizations.models import Hospital try: logger.info("Starting daily metrics calculation") target_date = (timezone.now() - timedelta(days=1)).date() summary_service = ExecutiveSummaryService() def _save_metrics(metrics, date, hospital=None): saved = 0 for metric_type, metric_value in metrics.items(): try: value = Decimal(str(metric_value)) except Exception: continue # Calculate simple variance vs previous day previous = ( ExecutiveMetric.objects.filter( metric_type=metric_type, hospital=hospital, metric_date__lt=date, ) .order_by("-metric_date") .first() ) variance = None variance_direction = "neutral" if previous and previous.metric_value is not None: prev_val = float(previous.metric_value) curr_val = float(value) if prev_val != 0: variance = Decimal(str(((curr_val - prev_val) / prev_val) * 100)) else: variance = Decimal("0") if variance > 0: variance_direction = "up" elif variance < 0: variance_direction = "down" ExecutiveMetric.objects.update_or_create( metric_date=date, metric_type=metric_type, hospital=hospital, defaults={ "metric_value": value, "variance": variance, "variance_direction": variance_direction, }, ) saved += 1 return saved # Calculate system-wide metrics logger.info(f"Calculating system-wide metrics for {target_date}") metrics = summary_service.aggregate_daily_metrics(target_date, hospital=None) system_saved = _save_metrics(metrics, target_date, hospital=None) # Calculate per-hospital metrics hospitals = Hospital.objects.filter(status="active") total_hospital_saved = 0 for hospital in hospitals: logger.info(f"Calculating metrics for {hospital.name}") metrics = summary_service.aggregate_daily_metrics(target_date, hospital=hospital) total_hospital_saved += _save_metrics(metrics, target_date, hospital=hospital) logger.info( f"Daily metrics calculated for {target_date}: {system_saved} system-wide, {total_hospital_saved} hospital-level" ) return { "status": "success", "date": target_date.isoformat(), "hospitals_processed": hospitals.count(), "system_metrics_saved": system_saved, "hospital_metrics_saved": total_hospital_saved, } except Exception as e: logger.error(f"Failed to calculate daily metrics: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)} @shared_task def generate_predictive_insights(): """ Generate predictive insights from data analysis. Runs every 6 hours. Analyzes trends, anomalies, SLA breach risks, and creates PredictiveInsight objects. """ from .services import PredictiveAnalyticsService try: logger.info("Starting predictive insights generation") predictive_service = PredictiveAnalyticsService() result = predictive_service.generate_predictive_insights() logger.info(f"Predictive insights generated: {result.get('insights_created', 0)} insights created") return { "status": "success", "insights_created": result.get("insights_created", 0), "insights_updated": result.get("insights_updated", 0), } except Exception as e: logger.error(f"Failed to generate predictive insights: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)} @shared_task def generate_ai_recommendations(): """ Generate AI recommendations from predictive insights. Runs daily at 3 AM. Creates AIRecommendation objects based on unhandled PredictiveInsights. """ from .services import RecommendationService try: logger.info("Starting AI recommendations generation") recommendation_service = RecommendationService() result = recommendation_service.generate_recommendations_from_insights() logger.info(f"AI recommendations generated: {result.get('recommendations_created', 0)}") return { "status": "success", "recommendations_created": result.get("recommendations_created", 0), } except Exception as e: logger.error(f"Failed to generate AI recommendations: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)} @shared_task def send_executive_pdf_report(): """ Send PDF reports to executives via email. Runs weekly and monthly after report generation. Emails the latest executive PDF report to all executive users. """ from django.core.mail import EmailMessage from django.conf import settings from .models import ExecutiveReport from apps.accounts.models import User try: logger.info("Starting executive PDF report email delivery") # Get latest completed report latest_report = ( ExecutiveReport.objects.filter(status="completed", pdf_file__isnull=False).order_by("-created_at").first() ) if not latest_report: logger.info("No completed PDF report found to send") return {"status": "skipped", "reason": "no_pdf_report"} # Get executive users executives = User.objects.filter( groups__name="Executive", is_active=True, ).distinct() if not executives.exists(): logger.info("No executive users found to send report to") return {"status": "skipped", "reason": "no_executives"} # Prepare email subject = f"Executive Summary Report - {latest_report.get_report_type_display()}" body = f"""Dear Executive, Please find attached the {latest_report.get_report_type_display().lower()} summary report for the period {latest_report.start_date} to {latest_report.end_date}. Key Highlights: {chr(10).join(f"• {highlight}" for highlight in latest_report.highlights_en[:5])} Key Concerns: {chr(10).join(f"• {concern}" for concern in latest_report.concerns_en[:5])} This report was generated automatically by the PX360 AI system. Best regards, PX360 Team """ # Send emails emails_sent = 0 for executive in executives: if executive.email: try: email = EmailMessage( subject=subject, body=body, to=[executive.email], ) email.attach_file(latest_report.pdf_file.path) email.send() emails_sent += 1 logger.info(f"Report sent to {executive.email}") except Exception as e: logger.error(f"Failed to send report to {executive.email}: {str(e)}") logger.info(f"PDF reports sent to {emails_sent} executives") return { "status": "success", "emails_sent": emails_sent, "report_id": str(latest_report.id), } except Exception as e: logger.error(f"Failed to send executive PDF reports: {str(e)}", exc_info=True) return {"status": "error", "error": str(e)}