""" Generate Monthly KPI Reports This command generates KPI reports for the previous month (or specified month) for all active hospitals. Should be run monthly via cron job. Usage: # Generate for previous month python manage.py generate_monthly_kpi_reports # Generate for specific month python manage.py generate_monthly_kpi_reports --year 2024 --month 12 # Generate for specific hospital python manage.py generate_monthly_kpi_reports --hospital-id # Generate specific report type python manage.py generate_monthly_kpi_reports --report-type resolution_72h # Dry run (don't save) python manage.py generate_monthly_kpi_reports --dry-run """ import logging from datetime import datetime from django.core.management.base import BaseCommand, CommandError from django.utils import timezone from apps.analytics.kpi_models import KPIReportType from apps.analytics.kpi_service import KPICalculationService from apps.organizations.models import Hospital logger = logging.getLogger(__name__) class Command(BaseCommand): help = "Generate monthly KPI reports for all hospitals" def add_arguments(self, parser): parser.add_argument( '--year', type=int, help='Year to generate report for (default: previous month year)' ) parser.add_argument( '--month', type=int, help='Month to generate report for (default: previous month)' ) parser.add_argument( '--hospital-id', type=str, help='Generate report for specific hospital only' ) parser.add_argument( '--report-type', type=str, choices=[rt[0] for rt in KPIReportType.choices], help='Generate specific report type only' ) parser.add_argument( '--dry-run', action='store_true', help='Show what would be generated without saving' ) parser.add_argument( '--force', action='store_true', help='Regenerate even if report already exists' ) def handle(self, *args, **options): # Determine year and month if options['year'] and options['month']: year = options['year'] month = options['month'] else: # Default to previous month today = timezone.now() if today.month == 1: year = today.year - 1 month = 12 else: year = today.year month = today.month - 1 self.stdout.write( self.style.NOTICE(f'Generating KPI reports for {year}-{month:02d}') ) # Get hospitals if options['hospital_id']: try: hospitals = Hospital.objects.filter(id=options['hospital_id']) if not hospitals.exists(): raise CommandError(f'Hospital with ID {options["hospital_id"]} not found') except Exception as e: raise CommandError(f'Invalid hospital ID: {e}') else: hospitals = Hospital.objects.filter(status='active') # Get report types if options['report_type']: report_types = [options['report_type']] else: report_types = [rt[0] for rt in KPIReportType.choices] # Statistics stats = { 'created': 0, 'updated': 0, 'skipped': 0, 'failed': 0, } # Generate reports for hospital in hospitals: self.stdout.write(f'\nProcessing hospital: {hospital.name}') for report_type in report_types: report_type_display = dict(KPIReportType.choices)[report_type] # Check if report already exists from apps.analytics.kpi_models import KPIReport existing = KPIReport.objects.filter( report_type=report_type, hospital=hospital, year=year, month=month ).first() if existing and not options['force']: self.stdout.write( f' - {report_type_display}: Already exists (skipping)' ) stats['skipped'] += 1 continue if options['dry_run']: self.stdout.write( self.style.SUCCESS(f' - {report_type_display}: Would generate (dry run)') ) continue try: # Generate the report report = KPICalculationService.generate_monthly_report( report_type=report_type, hospital=hospital, year=year, month=month, generated_by=None # Automated generation ) if existing: self.stdout.write( self.style.SUCCESS(f' - {report_type_display}: Regenerated') ) stats['updated'] += 1 else: self.stdout.write( self.style.SUCCESS(f' - {report_type_display}: Created') ) stats['created'] += 1 except Exception as e: self.stdout.write( self.style.ERROR(f' - {report_type_display}: Failed - {str(e)}') ) stats['failed'] += 1 logger.exception(f"Failed to generate {report_type} for {hospital.name}") # Summary self.stdout.write('\n' + '=' * 50) self.stdout.write(self.style.NOTICE('Summary:')) self.stdout.write(f' Created: {stats["created"]}') self.stdout.write(f' Updated: {stats["updated"]}') self.stdout.write(f' Skipped: {stats["skipped"]}') self.stdout.write(f' Failed: {stats["failed"]}') if stats['failed'] > 0: raise CommandError('Some reports failed to generate')