183 lines
6.2 KiB
Python
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')
|