""" Analytics & Reporting Views for IGNY8 Admin """ from django.contrib.admin.views.decorators import staff_member_required from django.shortcuts import render from django.db.models import Count, Sum, Avg, Q from django.utils import timezone from datetime import timedelta import json @staff_member_required def revenue_report(request): """Revenue and billing analytics""" from igny8_core.business.billing.models import Payment from igny8_core.auth.models import Plan # Date ranges today = timezone.now() months = [] monthly_revenue = [] for i in range(6): month_start = today.replace(day=1) - timedelta(days=30*i) month_end = month_start.replace(day=28) + timedelta(days=4) revenue = Payment.objects.filter( status='succeeded', processed_at__gte=month_start, processed_at__lt=month_end ).aggregate(total=Sum('amount'))['total'] or 0 months.insert(0, month_start.strftime('%b %Y')) monthly_revenue.insert(0, float(revenue)) # Plan distribution plan_distribution = Plan.objects.annotate( account_count=Count('accounts') ).values('name', 'account_count') # Payment method breakdown payment_methods = Payment.objects.filter( status='succeeded' ).values('payment_method').annotate( count=Count('id'), total=Sum('amount') ).order_by('-total') # Total revenue all time total_revenue = Payment.objects.filter( status='succeeded' ).aggregate(total=Sum('amount'))['total'] or 0 context = { 'title': 'Revenue Report', 'months': json.dumps(months), 'monthly_revenue': json.dumps(monthly_revenue), 'plan_distribution': list(plan_distribution), 'payment_methods': list(payment_methods), 'total_revenue': float(total_revenue), } # Merge with admin context from igny8_core.admin.site import admin_site admin_context = admin_site.each_context(request) context.update(admin_context) return render(request, 'admin/reports/revenue.html', context) @staff_member_required def usage_report(request): """Credit usage and AI operations analytics""" from igny8_core.business.billing.models import CreditUsageLog # Usage by operation type usage_by_operation = CreditUsageLog.objects.values( 'operation_type' ).annotate( total_credits=Sum('credits_used'), total_cost=Sum('cost_usd'), operation_count=Count('id') ).order_by('-total_credits') # Top credit consumers top_consumers = CreditUsageLog.objects.values( 'account__name' ).annotate( total_credits=Sum('credits_used'), operation_count=Count('id') ).order_by('-total_credits')[:10] # Model usage distribution model_usage = CreditUsageLog.objects.values( 'model_used' ).annotate( usage_count=Count('id') ).order_by('-usage_count') # Total credits used total_credits = CreditUsageLog.objects.aggregate( total=Sum('credits_used') )['total'] or 0 context = { 'title': 'Usage Report', 'usage_by_operation': list(usage_by_operation), 'top_consumers': list(top_consumers), 'model_usage': list(model_usage), 'total_credits': int(total_credits), } # Merge with admin context from igny8_core.admin.site import admin_site admin_context = admin_site.each_context(request) context.update(admin_context) return render(request, 'admin/reports/usage.html', context) @staff_member_required def content_report(request): """Content production analytics""" from igny8_core.modules.writer.models import Content, Tasks # Content by type content_by_type = Content.objects.values( 'content_type' ).annotate(count=Count('id')).order_by('-count') # Production timeline (last 30 days) days = [] daily_counts = [] for i in range(30): day = timezone.now().date() - timedelta(days=i) count = Content.objects.filter(created_at__date=day).count() days.insert(0, day.strftime('%m/%d')) daily_counts.insert(0, count) # Average word count by content type avg_words = Content.objects.values('content_type').annotate( avg_words=Avg('word_count') ).order_by('-avg_words') # Task completion rate total_tasks = Tasks.objects.count() completed_tasks = Tasks.objects.filter(status='completed').count() completion_rate = (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0 # Total content produced total_content = Content.objects.count() context = { 'title': 'Content Production Report', 'content_by_type': list(content_by_type), 'days': json.dumps(days), 'daily_counts': json.dumps(daily_counts), 'avg_words': list(avg_words), 'completion_rate': round(completion_rate, 1), 'total_content': total_content, 'total_tasks': total_tasks, 'completed_tasks': completed_tasks, } # Merge with admin context from igny8_core.admin.site import admin_site admin_context = admin_site.each_context(request) context.update(admin_context) return render(request, 'admin/reports/content.html', context) @staff_member_required def data_quality_report(request): """Check data quality and integrity""" issues = [] # Orphaned content (no site) from igny8_core.modules.writer.models import Content orphaned_content = Content.objects.filter(site__isnull=True).count() if orphaned_content > 0: issues.append({ 'severity': 'warning', 'type': 'Orphaned Records', 'count': orphaned_content, 'description': 'Content items without assigned site', 'action_url': '/admin/writer/content/?site__isnull=True' }) # Tasks without clusters from igny8_core.modules.writer.models import Tasks tasks_no_cluster = Tasks.objects.filter(cluster__isnull=True).count() if tasks_no_cluster > 0: issues.append({ 'severity': 'info', 'type': 'Missing Relationships', 'count': tasks_no_cluster, 'description': 'Tasks without assigned cluster', 'action_url': '/admin/writer/tasks/?cluster__isnull=True' }) # Accounts with negative credits from igny8_core.auth.models import Account negative_credits = Account.objects.filter(credits__lt=0).count() if negative_credits > 0: issues.append({ 'severity': 'error', 'type': 'Data Integrity', 'count': negative_credits, 'description': 'Accounts with negative credit balance', 'action_url': '/admin/igny8_core_auth/account/?credits__lt=0' }) # Duplicate keywords from igny8_core.modules.planner.models import Keywords duplicates = Keywords.objects.values('seed_keyword', 'site', 'sector').annotate( count=Count('id') ).filter(count__gt=1).count() if duplicates > 0: issues.append({ 'severity': 'warning', 'type': 'Duplicates', 'count': duplicates, 'description': 'Duplicate keywords for same site/sector', 'action_url': '/admin/planner/keywords/' }) # Content without SEO data no_seo = Content.objects.filter( Q(meta_title__isnull=True) | Q(meta_title='') | Q(meta_description__isnull=True) | Q(meta_description='') ).count() if no_seo > 0: issues.append({ 'severity': 'info', 'type': 'Incomplete Data', 'count': no_seo, 'description': 'Content missing SEO metadata', 'action_url': '/admin/writer/content/' }) context = { 'title': 'Data Quality Report', 'issues': issues, 'total_issues': len(issues), } # Merge with admin context from igny8_core.admin.site import admin_site admin_context = admin_site.each_context(request) context.update(admin_context) return render(request, 'admin/reports/data_quality.html', context)