""" Django management command to generate Saudi-influenced insurance claims data. Usage: python manage.py generate_saudi_claims --count 100 --tenant-id 1 python manage.py generate_saudi_claims --count 500 --all-tenants python manage.py generate_saudi_claims --count 50 --patient-id 123 """ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth import get_user_model from django.db import transaction from django.utils import timezone from patients.models import PatientProfile, InsuranceInfo, InsuranceClaim, ClaimStatusHistory from patients.data_generators.saudi_claims_generator import SaudiClaimsDataGenerator from core.models import Tenant import random User = get_user_model() class Command(BaseCommand): help = 'Generate Saudi-influenced insurance claims data for testing and development' def add_arguments(self, parser): parser.add_argument( '--count', type=int, default=50, help='Number of claims to generate (default: 50)' ) parser.add_argument( '--tenant-id', type=int, help='Specific tenant ID to generate claims for' ) parser.add_argument( '--all-tenants', action='store_true', help='Generate claims for all tenants' ) parser.add_argument( '--patient-id', type=int, help='Generate claims for a specific patient' ) parser.add_argument( '--insurance-id', type=int, help='Generate claims for a specific insurance policy' ) parser.add_argument( '--claim-type', choices=['MEDICAL', 'DENTAL', 'VISION', 'PHARMACY', 'EMERGENCY', 'INPATIENT', 'OUTPATIENT', 'PREVENTIVE', 'MATERNITY', 'MENTAL_HEALTH', 'REHABILITATION', 'DIAGNOSTIC', 'SURGICAL', 'CHRONIC_CARE'], help='Generate claims of specific type only' ) parser.add_argument( '--status', choices=['DRAFT', 'SUBMITTED', 'UNDER_REVIEW', 'APPROVED', 'PARTIALLY_APPROVED', 'DENIED', 'PAID', 'CANCELLED', 'APPEALED', 'RESUBMITTED'], help='Generate claims with specific status only' ) parser.add_argument( '--days-back', type=int, default=180, help='Generate claims from this many days back (default: 180)' ) parser.add_argument( '--dry-run', action='store_true', help='Show what would be generated without creating records' ) parser.add_argument( '--verbose', action='store_true', help='Show detailed output' ) parser.add_argument( '--clear-existing', action='store_true', help='Clear existing claims before generating new ones (USE WITH CAUTION)' ) def handle(self, *args, **options): """Main command handler.""" self.verbosity = options.get('verbosity', 1) self.verbose = options.get('verbose', False) try: # Validate options self._validate_options(options) # Get patients with insurance patients_with_insurance = self._get_patients_with_insurance(options) if not patients_with_insurance: raise CommandError("No patients with insurance found. Please create patients and insurance records first.") # Clear existing claims if requested if options['clear_existing']: self._clear_existing_claims(options) # Generate claims if options['dry_run']: self._dry_run(patients_with_insurance, options) else: self._generate_claims(patients_with_insurance, options) except Exception as e: raise CommandError(f"Error generating claims: {str(e)}") def _validate_options(self, options): """Validate command options.""" if options['tenant_id'] and options['all_tenants']: raise CommandError("Cannot specify both --tenant-id and --all-tenants") if options['count'] <= 0: raise CommandError("Count must be a positive integer") if options['days_back'] <= 0: raise CommandError("Days back must be a positive integer") def _get_patients_with_insurance(self, options): """Get patients with insurance based on options.""" patients_with_insurance = [] # Base queryset patients_query = PatientProfile.objects.select_related('tenant') insurance_query = InsuranceInfo.objects.select_related('patient', 'patient__tenant') # Filter by tenant if options['tenant_id']: try: tenant = Tenant.objects.get(id=options['tenant_id']) patients_query = patients_query.filter(tenant=tenant) insurance_query = insurance_query.filter(patient__tenant=tenant) if self.verbose: self.stdout.write(f"Filtering by tenant: {tenant.name}") except Tenant.DoesNotExist: raise CommandError(f"Tenant with ID {options['tenant_id']} not found") elif not options['all_tenants']: # Default to first tenant if no specific tenant specified first_tenant = Tenant.objects.first() if first_tenant: patients_query = patients_query.filter(tenant=first_tenant) insurance_query = insurance_query.filter(patient__tenant=first_tenant) if self.verbose: self.stdout.write(f"Using default tenant: {first_tenant.name}") # Filter by specific patient if options['patient_id']: try: patient = patients_query.get(id=options['patient_id']) insurance_query = insurance_query.filter(patient=patient) if self.verbose: self.stdout.write(f"Filtering by patient: {patient.get_full_name()}") except PatientProfile.DoesNotExist: raise CommandError(f"Patient with ID {options['patient_id']} not found") # Filter by specific insurance if options['insurance_id']: try: insurance = insurance_query.get(id=options['insurance_id']) patients_with_insurance = [(insurance.patient, insurance)] if self.verbose: self.stdout.write(f"Using specific insurance: {insurance.insurance_provider}") except InsuranceInfo.DoesNotExist: raise CommandError(f"Insurance with ID {options['insurance_id']} not found") else: # Get all patients with insurance for insurance in insurance_query: patients_with_insurance.append((insurance.patient, insurance)) return patients_with_insurance def _clear_existing_claims(self, options): """Clear existing claims if requested.""" if not options['clear_existing']: return self.stdout.write( self.style.WARNING("Clearing existing claims...") ) # Build filter for claims to delete claims_query = InsuranceClaim.objects.all() if options['tenant_id']: claims_query = claims_query.filter(patient__tenant_id=options['tenant_id']) elif not options['all_tenants']: first_tenant = Tenant.objects.first() if first_tenant: claims_query = claims_query.filter(patient__tenant=first_tenant) if options['patient_id']: claims_query = claims_query.filter(patient_id=options['patient_id']) if options['insurance_id']: claims_query = claims_query.filter(insurance_info_id=options['insurance_id']) deleted_count = claims_query.count() claims_query.delete() self.stdout.write( self.style.SUCCESS(f"Cleared {deleted_count} existing claims") ) def _dry_run(self, patients_with_insurance, options): """Show what would be generated without creating records.""" self.stdout.write( self.style.WARNING("DRY RUN - No records will be created") ) generator = SaudiClaimsDataGenerator() # Generate sample claims data sample_claims = generator.generate_multiple_claims( patients_with_insurance[:min(5, len(patients_with_insurance))], min(5, options['count']) ) self.stdout.write(f"\nWould generate {options['count']} claims") self.stdout.write(f"Available patients with insurance: {len(patients_with_insurance)}") if sample_claims: self.stdout.write(f"\nSample claim data:") for i, claim in enumerate(sample_claims[:3], 1): self.stdout.write(f"\nClaim {i}:") self.stdout.write(f" - Number: {claim['claim_number']}") self.stdout.write(f" - Patient: {claim['patient'].get_full_name()}") self.stdout.write(f" - Type: {claim['claim_type']}") self.stdout.write(f" - Status: {claim['status']}") self.stdout.write(f" - Amount: {claim['billed_amount']} SAR") self.stdout.write(f" - Provider: {claim['service_provider']}") self.stdout.write(f" - Facility: {claim['facility_name']}") # Show statistics stats = generator.get_saudi_insurance_statistics(sample_claims) if stats: self.stdout.write(f"\nSample statistics:") self.stdout.write(f" - Total claims: {stats['total_claims']}") self.stdout.write(f" - Approval rate: {stats['approval_rate']:.1f}%") self.stdout.write(f" - Average amount: {stats['average_claim_amount_sar']:.2f} SAR") def _generate_claims(self, patients_with_insurance, options): """Generate the actual claims records.""" generator = SaudiClaimsDataGenerator() created_by = self._get_system_user() self.stdout.write(f"Generating {options['count']} insurance claims...") if self.verbose: self.stdout.write(f"Available patients with insurance: {len(patients_with_insurance)}") # Generate claims data claims_data = generator.generate_multiple_claims( patients_with_insurance, options['count'], created_by ) # Filter by claim type if specified if options['claim_type']: claims_data = [c for c in claims_data if c['claim_type'] == options['claim_type']] if self.verbose: self.stdout.write(f"Filtered to {len(claims_data)} claims of type {options['claim_type']}") # Filter by status if specified if options['status']: claims_data = [c for c in claims_data if c['status'] == options['status']] if self.verbose: self.stdout.write(f"Filtered to {len(claims_data)} claims with status {options['status']}") if not claims_data: self.stdout.write( self.style.WARNING("No claims data generated after filtering") ) return # Create claims in database created_claims = [] created_count = 0 with transaction.atomic(): for claim_data in claims_data: try: # Create the claim claim = InsuranceClaim.objects.create(**claim_data) created_claims.append(claim) created_count += 1 # Create status history self._create_status_history(claim, created_by) if self.verbose and created_count % 10 == 0: self.stdout.write(f"Created {created_count} claims...") except Exception as e: self.stdout.write( self.style.ERROR(f"Error creating claim: {str(e)}") ) continue # Show results self.stdout.write( self.style.SUCCESS(f"Successfully created {created_count} insurance claims") ) # Show statistics if created_claims: stats = generator.get_saudi_insurance_statistics(claims_data) self.stdout.write(f"\nGenerated claims statistics:") self.stdout.write(f" - Total claims: {stats['total_claims']}") self.stdout.write(f" - Approved claims: {stats['approved_claims']}") self.stdout.write(f" - Denied claims: {stats['denied_claims']}") self.stdout.write(f" - Pending claims: {stats['pending_claims']}") self.stdout.write(f" - Approval rate: {stats['approval_rate']:.1f}%") self.stdout.write(f" - Total billed: {stats['total_billed_sar']:,.2f} SAR") self.stdout.write(f" - Total approved: {stats['total_approved_sar']:,.2f} SAR") self.stdout.write(f" - Average claim: {stats['average_claim_amount_sar']:,.2f} SAR") # Show sample claims if self.verbose and created_claims: self.stdout.write(f"\nSample created claims:") for claim in created_claims[:3]: self.stdout.write(f" - {claim.claim_number}: {claim.patient.get_full_name()} - {claim.billed_amount} SAR ({claim.status})") def _create_status_history(self, claim, created_by): """Create status history for the claim.""" # Create initial status history entry ClaimStatusHistory.objects.create( claim=claim, from_status=None, to_status='DRAFT', reason='Initial claim creation', changed_by=created_by ) # If claim has progressed beyond draft, create additional history if claim.status != 'DRAFT': statuses = ['DRAFT', 'SUBMITTED', 'UNDER_REVIEW'] if claim.status in ['APPROVED', 'PARTIALLY_APPROVED', 'PAID']: statuses.append('APPROVED') if claim.status == 'PAID': statuses.append('PAID') elif claim.status == 'DENIED': statuses.append('DENIED') # Create history entries for each status transition for i in range(1, len(statuses)): ClaimStatusHistory.objects.create( claim=claim, from_status=statuses[i-1], to_status=statuses[i], reason=f'Automatic status progression to {statuses[i]}', changed_by=created_by ) def _get_system_user(self): """Get or create a system user for created_by field.""" try: return User.objects.get(username='system') except User.DoesNotExist: # Try to get the first superuser superuser = User.objects.filter(is_superuser=True).first() if superuser: return superuser # Try to get any user user = User.objects.first() if user: return user # Create a system user if none exists return User.objects.create_user( username='system', email='system@hospital.local', first_name='System', last_name='Generator', is_active=True )