HH/apps/analytics/management/commands/generate_monthly_kpi_reports.py
2026-02-22 08:35:53 +03:00

183 lines
6.2 KiB
Python

"""
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 <uuid>
# 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')