from django.core.management.base import BaseCommand from django.utils import timezone from django.db import transaction from django.db.models import Avg from datetime import timedelta, datetime import random from tqdm import tqdm from apps.surveys.models import ( SurveyTemplate, SurveyQuestion, SurveyInstance, SurveyResponse, SurveyTracking, SurveyStatus ) from apps.organizations.models import Hospital, Patient class Command(BaseCommand): help = 'Seed historical survey data for analytics (1 year timeframe)' def add_arguments(self, parser): parser.add_argument( '--months', type=int, default=12, help='Number of months of historical data to generate (default: 12)' ) parser.add_argument( '--surveys-per-month', type=int, default=300, help='Number of surveys to generate per month (default: 300)' ) parser.add_argument( '--clear', action='store_true', help='Clear existing survey instances before seeding' ) def handle(self, *args, **options): months = options['months'] surveys_per_month = options['surveys_per_month'] clear = options['clear'] self.stdout.write(self.style.SUCCESS('='*70)) self.stdout.write(self.style.SUCCESS('SEEDING HISTORICAL SURVEY DATA')) self.stdout.write(self.style.SUCCESS('='*70)) # Get hospital hospital = Hospital.objects.filter(status='active').first() if not hospital: self.stdout.write(self.style.ERROR('No active hospital found!')) return self.stdout.write(f'Using hospital: {hospital.name}') # Clear existing data if requested if clear: self.stdout.write('\nClearing existing survey instances...') deleted_count = SurveyInstance.objects.all().delete()[0] self.stdout.write(f'āœ“ Deleted {deleted_count} survey instances') # Get or create survey templates self.stdout.write('\nChecking survey templates...') templates = self.get_or_create_templates(hospital) self.stdout.write(f'āœ“ Found/created {len(templates)} survey templates') # Get patients patients = list(Patient.objects.all()) if not patients: self.stdout.write(self.style.ERROR('No patients found! Please create patients first.')) return self.stdout.write(f'āœ“ Found {len(patients)} patients') # Generate surveys total_surveys = months * surveys_per_month self.stdout.write(f'\nGenerating {total_surveys} surveys over {months} months...\n') with transaction.atomic(): # Generate surveys month by month current_date = timezone.now() all_surveys = [] for month_offset in tqdm(range(months), desc="Generating surveys", unit="month"): month_date = current_date - timedelta(days=month_offset * 30) surveys_for_month = self.generate_monthly_surveys( templates, patients, hospital, month_date, surveys_per_month ) all_surveys.extend(surveys_for_month) self.stdout.write(f'\n Generated {len(surveys_for_month)} surveys for month {month_offset + 1}') # Display summary self.stdout.write('\n' + '='*70) self.stdout.write(self.style.SUCCESS('GENERATION COMPLETE')) self.stdout.write('='*70) # Statistics completed = SurveyInstance.objects.filter(status='completed').count() negative = SurveyInstance.objects.filter(is_negative=True).count() with_comments = SurveyInstance.objects.exclude(comment='').count() # Score distribution self.stdout.write(f'\nTotal Surveys: {SurveyInstance.objects.count()}') self.stdout.write(f'Completed: {completed} ({completed/total_surveys*100:.1f}%)') self.stdout.write(f'Negative: {negative} ({negative/completed*100 if completed > 0 else 0:.1f}% of completed)') self.stdout.write(f'With Comments: {with_comments}') # By template self.stdout.write('\nBy Survey Template:') for template in templates: count = SurveyInstance.objects.filter(survey_template=template).count() avg_score = SurveyInstance.objects.filter( survey_template=template, total_score__isnull=False ).aggregate(avg=Avg('total_score'))['avg'] avg_score_display = f'{avg_score:.2f}' if avg_score else 'N/A' self.stdout.write( f' {template.name}: {count} surveys ' f'(avg score: {avg_score_display})' ) self.stdout.write(self.style.SUCCESS('\nāœ“ Historical survey data seeded successfully!')) def get_or_create_templates(self, hospital): """Get or create survey templates for all patient types""" template_definitions = [ { 'name': 'Inpatient Post-Discharge Survey', 'survey_type': 'stage', 'questions': [ 'How satisfied were you with the nursing care?', 'How satisfied were you with the doctor\'s care?', 'How clean was your room?', 'How satisfied were you with the food quality?', 'How well were you informed about your treatment?', 'How likely are you to recommend this hospital to others?' ] }, { 'name': 'OPD Patient Experience Survey', 'survey_type': 'stage', 'questions': [ 'How satisfied were you with the registration process?', 'How satisfied were you with the waiting time?', 'How satisfied were you with the doctor\'s consultation?', 'How satisfied were you with the pharmacy service?', 'How likely are you to recommend this hospital to others?' ] }, { 'name': 'EMS Emergency Services Survey', 'survey_type': 'stage', 'questions': [ 'How satisfied were you with the ambulance response time?', 'How satisfied were you with the paramedic care?', 'How satisfied were you with the emergency department care?', 'How satisfied were you with the communication from staff?', 'How likely are you to recommend this hospital to others?' ] }, { 'name': 'Day Case Patient Survey', 'survey_type': 'stage', 'questions': [ 'How satisfied were you with the pre-procedure preparation?', 'How satisfied were you with the procedure itself?', 'How satisfied were you with the post-procedure care?', 'How satisfied were you with the discharge process?', 'How likely are you to recommend this hospital to others?' ] } ] templates = [] for template_def in template_definitions: template, created = SurveyTemplate.objects.get_or_create( name=template_def['name'], hospital=hospital, defaults={ 'survey_type': template_def['survey_type'], 'is_active': True } ) # Create questions if template was just created if created: for order, question_text in enumerate(template_def['questions'], start=1): question_type = 'nps' if 'recommend' in question_text.lower() else 'rating' SurveyQuestion.objects.create( survey_template=template, text=question_text, question_type=question_type, order=order, is_required=True ) templates.append(template) return templates def generate_monthly_surveys(self, templates, patients, hospital, month_date, count): """Generate surveys for a specific month""" surveys = [] # Distribute surveys across the month for i in range(count): # Random day within the month day_offset = random.randint(0, 29) survey_date = month_date + timedelta(days=day_offset) # Select random template and patient template = random.choice(templates) patient = random.choice(patients) # Determine status (weighted distribution) status_roll = random.random() if status_roll < 0.85: status = SurveyStatus.COMPLETED elif status_roll < 0.95: status = SurveyStatus.ABANDONED elif status_roll < 0.98: status = SurveyStatus.IN_PROGRESS else: status = SurveyStatus.VIEWED # Create survey instance survey = SurveyInstance.objects.create( survey_template=template, patient=patient, hospital=hospital, status=status, sent_at=survey_date, delivery_channel=random.choice(['sms', 'whatsapp', 'email']), recipient_phone=patient.phone, recipient_email=patient.email, ) # Set timestamps based on status if status == SurveyStatus.COMPLETED: survey.opened_at = survey.sent_at + timedelta(minutes=random.randint(5, 60)) survey.completed_at = survey.opened_at + timedelta(minutes=random.randint(2, 15)) survey.time_spent_seconds = random.randint(60, 900) survey.open_count = random.randint(1, 3) survey.last_opened_at = survey.completed_at elif status == SurveyStatus.ABANDONED: survey.opened_at = survey.sent_at + timedelta(minutes=random.randint(5, 60)) survey.time_spent_seconds = random.randint(30, 300) survey.open_count = 1 survey.last_opened_at = survey.opened_at elif status == SurveyStatus.IN_PROGRESS: survey.opened_at = survey.sent_at + timedelta(minutes=random.randint(5, 60)) survey.open_count = random.randint(1, 2) survey.last_opened_at = survey.opened_at elif status == SurveyStatus.VIEWED: survey.opened_at = survey.sent_at + timedelta(minutes=random.randint(5, 60)) survey.open_count = 1 survey.last_opened_at = survey.opened_at survey.save() # Generate responses for completed surveys if status == SurveyStatus.COMPLETED: self.generate_survey_responses(survey, template) # Add comment for some surveys (more common for negative ones) comment_roll = random.random() should_add_comment = ( comment_roll < 0.25 if survey.is_negative else comment_roll < 0.15 ) if should_add_comment: survey.comment = self.generate_comment(survey.is_negative) survey.save(update_fields=['comment']) # Create tracking events self.create_tracking_events(survey) surveys.append(survey) return surveys def generate_survey_responses(self, survey, template): """Generate responses for a survey instance""" questions = template.questions.all() # Determine target score range (weighted distribution) score_roll = random.random() if score_roll < 0.35: # Excellent (4.5-5.0) min_score, max_score = 4.5, 5.0 elif score_roll < 0.65: # Good (4.0-4.4) min_score, max_score = 4.0, 4.4 elif score_roll < 0.85: # Average (3.5-3.9) min_score, max_score = 3.5, 3.9 elif score_roll < 0.95: # Poor (3.0-3.4) min_score, max_score = 3.0, 3.4 else: # Negative (below 3.0) min_score, max_score = 1.5, 2.9 # Generate responses for each question for question in questions: # Generate score within target range if question.question_type == 'rating': score = round(random.uniform(min_score, max_score), 1) SurveyResponse.objects.create( survey_instance=survey, question=question, numeric_value=score ) elif question.question_type == 'nps': # NPS: 0-10 scale if min_score >= 4.5: nps_score = random.randint(9, 10) # Promoters elif min_score >= 4.0: nps_score = random.randint(7, 8) # Passive elif min_score >= 3.5: nps_score = random.randint(5, 6) # Passive elif min_score >= 3.0: nps_score = random.randint(3, 4) # Detractors else: nps_score = random.randint(0, 2) # Detractors SurveyResponse.objects.create( survey_instance=survey, question=question, numeric_value=nps_score ) # Calculate score score = survey.calculate_score() def create_tracking_events(self, survey): """Create tracking events for a survey""" # Page view SurveyTracking.objects.create( survey_instance=survey, event_type='page_view', user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', ip_address='192.168.1.' + str(random.randint(1, 254)), device_type=random.choice(['mobile', 'mobile', 'desktop']), browser='Safari 14.0', created_at=survey.sent_at ) # Survey started if survey.opened_at: SurveyTracking.objects.create( survey_instance=survey, event_type='survey_started', user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', ip_address='192.168.1.' + str(random.randint(1, 254)), device_type=random.choice(['mobile', 'mobile', 'desktop']), browser='Safari 14.0', created_at=survey.opened_at, metadata={'referrer': 'https://wa.me/'} ) # Survey completed if survey.completed_at: SurveyTracking.objects.create( survey_instance=survey, event_type='survey_completed', user_agent='Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', ip_address='192.168.1.' + str(random.randint(1, 254)), device_type=random.choice(['mobile', 'mobile', 'desktop']), browser='Safari 14.0', created_at=survey.completed_at, total_time_spent=survey.time_spent_seconds, metadata={ 'response_count': survey.survey_template.questions.count() } ) def generate_comment(self, is_negative): """Generate a realistic comment based on sentiment""" positive_comments = [ "Excellent service, very satisfied with everything!", "Staff was very professional and caring. Great experience.", "Clean facilities and friendly staff. Highly recommend.", "The doctors and nurses were amazing. Thank you!", "Very good care, would definitely come back.", "Professional service and attentive staff.", "Everything was perfect, no complaints at all.", "Best hospital experience I've ever had.", "Staff went above and beyond to help me.", "Quick service and excellent medical care." ] negative_comments = [ "The waiting time was too long, very frustrating.", "Staff seemed rushed and didn't explain things well.", "Facilities were not clean enough, needs improvement.", "Communication with staff was poor and confusing.", "Had to wait too long for test results.", "The staff was not very friendly or helpful.", "Overall experience was disappointing, needs work.", "Not satisfied with the level of care provided.", "The process was disorganized and slow.", "Expected better service and attention to detail." ] if is_negative: return random.choice(negative_comments) else: return random.choice(positive_comments)