HH/apps/surveys/management/commands/seed_historical_surveys.py

413 lines
17 KiB
Python

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)