""" Survey analytics API views. Provides endpoints for accessing survey engagement metrics: - Engagement statistics - Patient survey timelines - Completion times - Abandonment analysis - Hourly activity """ import json import os from datetime import datetime from django.conf import settings from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from django.http import FileResponse, HttpResponse from .analytics import ( get_survey_engagement_stats, get_patient_survey_timeline, get_survey_completion_times, get_survey_abandonment_analysis, get_hourly_survey_activity, ) from .analytics_utils import ( calculate_survey_analytics, generate_markdown_report, generate_html_report, generate_enhanced_survey_reports, generate_single_template_report, ) from .models import SurveyInstance, SurveyTracking from .serializers import SurveyInstanceSerializer, SurveyTrackingSerializer class SurveyAnalyticsViewSet(viewsets.ViewSet): """ Survey analytics API. Provides various metrics about survey engagement: - Engagement rates (open, completion, abandonment) - Time metrics (time to complete, time spent) - Patient timelines - Abandonment patterns - Hourly activity """ permission_classes = [IsAuthenticated] @action(detail=False, methods=['get']) def engagement_stats(self, request): """ Get overall survey engagement statistics. Query params: - survey_template_id: Filter by survey template (optional) - hospital_id: Filter by hospital (optional) - days: Number of days to look back (default: 30) """ survey_template_id = request.query_params.get('survey_template_id') hospital_id = request.query_params.get('hospital_id') days = int(request.query_params.get('days', 30)) try: stats = get_survey_engagement_stats( survey_template_id=survey_template_id, hospital_id=hospital_id, days=days ) return Response(stats) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def patient_timeline(self, request): """ Get survey timeline for a specific patient. Query params: - patient_id: Required - Patient ID """ patient_id = request.query_params.get('patient_id') if not patient_id: return Response( {'error': 'patient_id is required'}, status=status.HTTP_400_BAD_REQUEST ) try: timeline = get_patient_survey_timeline(patient_id) return Response({'timeline': timeline}) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def completion_times(self, request): """ Get individual survey completion times. Query params: - survey_template_id: Filter by survey template (optional) - hospital_id: Filter by hospital (optional) - days: Number of days to look back (default: 30) """ survey_template_id = request.query_params.get('survey_template_id') hospital_id = request.query_params.get('hospital_id') days = int(request.query_params.get('days', 30)) try: completion_times = get_survey_completion_times( survey_template_id=survey_template_id, hospital_id=hospital_id, days=days ) return Response({'completion_times': completion_times}) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def abandonment_analysis(self, request): """ Analyze abandoned surveys to identify patterns. Query params: - survey_template_id: Filter by survey template (optional) - hospital_id: Filter by hospital (optional) - days: Number of days to look back (default: 30) """ survey_template_id = request.query_params.get('survey_template_id') hospital_id = request.query_params.get('hospital_id') days = int(request.query_params.get('days', 30)) try: analysis = get_survey_abandonment_analysis( survey_template_id=survey_template_id, hospital_id=hospital_id, days=days ) return Response(analysis) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def hourly_activity(self, request): """ Get survey activity by hour. Query params: - hospital_id: Filter by hospital (optional) - days: Number of days to look back (default: 7) """ hospital_id = request.query_params.get('hospital_id') days = int(request.query_params.get('days', 7)) try: activity = get_hourly_survey_activity( hospital_id=hospital_id, days=days ) return Response({'activity': activity}) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def summary_dashboard(self, request): """ Get a comprehensive summary dashboard with all key metrics. Query params: - hospital_id: Filter by hospital (optional) - days: Number of days to look back (default: 30) """ hospital_id = request.query_params.get('hospital_id') days = int(request.query_params.get('days', 30)) try: # Get engagement stats engagement = get_survey_engagement_stats( hospital_id=hospital_id, days=days ) # Get abandonment analysis abandonment = get_survey_abandonment_analysis( hospital_id=hospital_id, days=days ) # Get top 10 fastest completions fastest_completions = get_survey_completion_times( hospital_id=hospital_id, days=days )[:10] # Get top 10 slowest completions slowest_completions = get_survey_completion_times( hospital_id=hospital_id, days=days )[-10:] if len(get_survey_completion_times(hospital_id=hospital_id, days=days)) > 10 else [] return Response({ 'engagement': engagement, 'abandonment': abandonment, 'fastest_completions': fastest_completions, 'slowest_completions': slowest_completions, }) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['post']) def generate_report(self, request): """ Generate comprehensive survey analytics report. Request body (JSON): { "template": "Survey template name (optional)", "start_date": "YYYY-MM-DD (optional)", "end_date": "YYYY-MM-DD (optional)", "format": "json|html|markdown (optional, default: json)", "save": true/false (optional, default: false) } Returns: - If format=json: Returns analytics data as JSON - If format=html: Returns HTML content - If format=markdown: Returns Markdown content - If save=true: Also saves to file and returns file path """ try: data = request.data template = data.get('template') start_date = data.get('start_date') end_date = data.get('end_date') report_format = data.get('format', 'json') save_to_file = data.get('save', False) # Parse dates if provided start_dt = None end_dt = None if start_date: start_dt = datetime.strptime(start_date, '%Y-%m-%d').date() if end_date: end_dt = datetime.strptime(end_date, '%Y-%m-%d').date() # Generate analytics data analytics_data = calculate_survey_analytics( template_name=template, start_date=start_dt, end_date=end_dt ) # Format output if report_format == 'json': response_data = { 'success': True, 'data': analytics_data } if save_to_file: # Save to JSON file output_dir = getattr(settings, 'SURVEY_REPORTS_DIR', 'reports') os.makedirs(output_dir, exist_ok=True) filepath = os.path.join(output_dir, f'survey_analytics_data_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json') with open(filepath, 'w', encoding='utf-8') as f: json.dump(analytics_data, f, indent=2, default=str) response_data['saved_to'] = filepath return Response(response_data) elif report_format == 'html': # Generate HTML report html_content = generate_html_report(analytics_data) response_data = { 'success': True, 'content': html_content } if save_to_file: # Save to HTML file output_dir = getattr(settings, 'SURVEY_REPORTS_DIR', 'reports') os.makedirs(output_dir, exist_ok=True) filepath = os.path.join(output_dir, f'survey_analytics_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.html') with open(filepath, 'w', encoding='utf-8') as f: f.write(html_content) response_data['saved_to'] = filepath return Response(response_data) elif report_format == 'markdown': # Generate Markdown report markdown_content = generate_markdown_report(analytics_data) response_data = { 'success': True, 'content': markdown_content } if save_to_file: # Save to Markdown file output_dir = getattr(settings, 'SURVEY_REPORTS_DIR', 'reports') os.makedirs(output_dir, exist_ok=True) filepath = os.path.join(output_dir, f'survey_analytics_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.md') with open(filepath, 'w', encoding='utf-8') as f: f.write(markdown_content) response_data['saved_to'] = filepath return Response(response_data) else: return Response( {'error': f'Invalid format: {report_format}. Use json, html, or markdown.'}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: import traceback traceback.print_exc() return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['get']) def available_reports(self, request): """ List available survey analytics reports. Returns list of JSON, HTML, and Markdown reports in the reports directory. """ try: output_dir = getattr(settings, 'SURVEY_REPORTS_DIR', 'reports') if not os.path.exists(output_dir): return Response({ 'success': True, 'reports': [] }) reports = [] for filename in os.listdir(output_dir): filepath = os.path.join(output_dir, filename) if os.path.isfile(filepath): stat = os.stat(filepath) report_type = 'unknown' if filename.endswith('.json'): report_type = 'json' elif filename.endswith('.html'): report_type = 'html' elif filename.endswith('.md'): report_type = 'markdown' reports.append({ 'filename': filename, 'type': report_type, 'size': stat.st_size, 'created': datetime.fromtimestamp(stat.st_ctime).isoformat(), 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(), }) # Sort by creation date (newest first) reports.sort(key=lambda x: x['created'], reverse=True) return Response({ 'success': True, 'reports': reports }) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) @action(detail=False, methods=['post']) def generate_enhanced_reports(self, request): """ Generate enhanced survey analytics reports - one per survey template. Creates separate HTML reports for each survey template with: - Detailed question-level analysis - Individual charts per question - Performance insights and recommendations - Master index page linking all reports Request body (JSON): { "template": "Survey template name (optional - if not provided, generates for all)", "start_date": "YYYY-MM-DD (optional)", "end_date": "YYYY-MM-DD (optional)" } Returns: { "success": true, "reports_dir": "/path/to/reports/reports_20240208_123045", "index_url": "/surveys/reports/reports_20240208_123045/index.html", "reports": [ { "template_name": "Inpatient Satisfaction", "filename": "inpatient_satisfaction.html", "size": 15234 } ] } """ try: data = request.data template = data.get('template') start_date = data.get('start_date') end_date = data.get('end_date') # Parse dates if provided start_dt = None end_dt = None if start_date: start_dt = datetime.strptime(start_date, '%Y-%m-%d').date() if end_date: end_dt = datetime.strptime(end_date, '%Y-%m-%d').date() # Generate enhanced reports result = generate_enhanced_survey_reports( template_name=template, start_date=start_dt, end_date=end_dt ) # Build URLs for accessing reports import os from django.conf import settings reports_dir_name = os.path.basename(result['reports_dir']) return Response({ 'success': True, 'message': f"Generated {len(result['individual_reports'])} reports", 'reports_dir': result['reports_dir'], 'index_path': result['index_path'], 'summary_path': result['summary_path'], 'reports': result['individual_reports'], 'view_url': f"/surveys/enhanced-reports/{reports_dir_name}/" }) except Exception as e: import traceback traceback.print_exc() return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST ) class SurveyTrackingViewSet(viewsets.ReadOnlyModelViewSet): """ Survey tracking events API. View detailed tracking events for surveys: - Page views - Completions - Abandonments - Device/browser information """ queryset = SurveyTracking.objects.select_related( 'survey_instance', 'survey_instance__patient', 'survey_instance__survey_template' ).order_by('-created_at') serializer_class = SurveyTrackingSerializer permission_classes = [IsAuthenticated] filterset_fields = ['survey_instance', 'event_type', 'device_type', 'browser'] search_fields = ['ip_address', 'user_agent'] @action(detail=False, methods=['get']) def by_survey(self, request): """ Get tracking events for a specific survey instance. Query params: - survey_instance_id: Required - SurveyInstance ID """ survey_instance_id = request.query_params.get('survey_instance_id') if not survey_instance_id: return Response( {'error': 'survey_instance_id is required'}, status=status.HTTP_400_BAD_REQUEST ) try: tracking_events = self.queryset.filter( survey_instance_id=survey_instance_id ) # Serialize and return serializer = self.get_serializer(tracking_events, many=True) return Response(serializer.data) except Exception as e: return Response( {'error': str(e)}, status=status.HTTP_400_BAD_REQUEST )