""" Management command to generate Saudi-influenced test data for all apps. This command creates realistic test data with Saudi cultural context including: - Arabic names (both English and Arabic) - Saudi phone numbers and national IDs - Saudi cities and addresses - SAR currency - Saudi healthcare context """ import random import secrets from datetime import date, datetime, timedelta from decimal import Decimal from django.core.management.base import BaseCommand from django.db import transaction from django.utils import timezone from faker import Faker # Import all models from core.models import ( Tenant, User, Patient, Clinic, File, SubFile, Consent, ConsentTemplate, Attachment, AuditLog, SettingTemplate, TenantSetting ) from appointments.models import ( Provider, Room, Schedule, Appointment, AppointmentReminder, AppointmentConfirmation ) from finance.models import ( Service, Package, Payer, Invoice, InvoiceLineItem, Payment, PackagePurchase, CSID ) from notifications.models import ( MessageTemplate, Message, NotificationPreference, MessageLog ) from referrals.models import Referral from integrations.models import ( ExternalOrder, NphiesMessage, NphiesEncounterLink, EInvoice, ZatcaCredential, PayerContract ) from medical.models import ( MedicalConsultation, MedicationPlan, MedicalFollowUp, ConsultationResponse, ConsultationFeedback ) from nursing.models import NursingEncounter, GrowthChart, VitalSignsAlert from aba.models import ABAConsult, ABABehavior, ABAGoal, ABASession, ABASkillTarget from ot.models import OTConsult, OTSession, OTTargetSkill, OTProgressReport from slp.models import SLPConsult, SLPAssessment, SLPIntervention, SLPTarget, SLPProgressReport from hr.models import Attendance, Schedule as HRSchedule, Holiday, LeaveRequest, LeaveBalance from documents.models import DocumentTemplate, ClinicalNote, NoteAddendum, NoteAuditLog # Saudi-specific data SAUDI_MALE_NAMES_EN = [ 'Mohammed', 'Abdullah', 'Fahad', 'Sultan', 'Khalid', 'Faisal', 'Saud', 'Turki', 'Abdulaziz', 'Abdulrahman', 'Omar', 'Ali', 'Hassan', 'Hussein', 'Saleh', 'Saad', 'Nasser', 'Mansour', 'Bandar', 'Nawaf', 'Talal', 'Waleed', 'Yazeed', 'Ziyad' ] SAUDI_MALE_NAMES_AR = [ 'محمد', 'عبدالله', 'فهد', 'سلطان', 'خالد', 'فيصل', 'سعود', 'تركي', 'عبدالعزيز', 'عبدالرحمن', 'عمر', 'علي', 'حسن', 'حسين', 'صالح', 'سعد', 'ناصر', 'منصور', 'بندر', 'نواف', 'طلال', 'وليد', 'يزيد', 'زياد' ] SAUDI_FEMALE_NAMES_EN = [ 'Noura', 'Fatima', 'Sarah', 'Maha', 'Hessa', 'Lama', 'Reem', 'Amal', 'Hind', 'Latifa', 'Munira', 'Abeer', 'Hanan', 'Jawaher', 'Lulwa', 'Mona', 'Nada', 'Rania', 'Salma', 'Wafa', 'Basma', 'Dalal', 'Ghada', 'Hayat' ] SAUDI_FEMALE_NAMES_AR = [ 'نورة', 'فاطمة', 'سارة', 'مها', 'حصة', 'لمى', 'ريم', 'أمل', 'هند', 'لطيفة', 'منيرة', 'عبير', 'حنان', 'جواهر', 'لولوة', 'منى', 'ندى', 'رانيا', 'سلمى', 'وفاء', 'بسمة', 'دلال', 'غادة', 'حياة' ] SAUDI_FAMILY_NAMES_EN = [ 'Al-Otaibi', 'Al-Ghamdi', 'Al-Qahtani', 'Al-Harbi', 'Al-Zahrani', 'Al-Shammari', 'Al-Dosari', 'Al-Mutairi', 'Al-Maliki', 'Al-Subai', 'Al-Rashidi', 'Al-Ahmadi', 'Al-Anzi', 'Al-Juaid', 'Al-Shahrani', 'Al-Yami', 'Al-Balawi', 'Al-Shamrani', 'Al-Dawsari', 'Al-Subaie', 'Al-Harthi', 'Al-Omari', 'Al-Shehri', 'Al-Asiri' ] SAUDI_FAMILY_NAMES_AR = [ 'العتيبي', 'الغامدي', 'القحطاني', 'الحربي', 'الزهراني', 'الشمري', 'الدوسري', 'المطيري', 'المالكي', 'السبيعي', 'الرشيدي', 'الأحمدي', 'العنزي', 'الجعيد', 'الشهراني', 'اليامي', 'البلوي', 'الشمراني', 'الدوسري', 'السبيعي', 'الحارثي', 'العمري', 'الشهري', 'العسيري' ] SAUDI_CITIES = [ 'Riyadh', 'Jeddah', 'Mecca', 'Medina', 'Dammam', 'Khobar', 'Dhahran', 'Taif', 'Buraidah', 'Tabuk', 'Khamis Mushait', 'Hail', 'Najran', 'Jubail', 'Abha', 'Yanbu', 'Al-Kharj', 'Al-Hasa', 'Qatif', 'Arar' ] RIYADH_DISTRICTS = [ 'Al-Olaya', 'Al-Malaz', 'Al-Naseem', 'Al-Rawdah', 'Al-Muruj', 'Al-Nakheel', 'Al-Sahafa', 'Al-Yasmin', 'Al-Narjis', 'Al-Aqiq', 'Granada', 'Hittin', 'King Fahd', 'Al-Wurud', 'Al-Hamra', 'Al-Sulimaniyah' ] SAUDI_INSURANCE_COMPANIES = [ 'Bupa Arabia', 'Tawuniya', 'Medgulf', 'Malath', 'Al-Rajhi Takaful', 'Saico', 'Walaa', 'Solidarity', 'Gulf Union', 'Salama' ] SAUDI_MOBILE_PREFIXES = ['50', '53', '54', '55', '56', '57', '58', '59'] class SaudiDataGenerator: """Helper class to generate Saudi-specific data.""" def __init__(self): self.fake = Faker(['ar_SA', 'en_US']) self.fake_ar = Faker('ar_SA') self.fake_en = Faker('en_US') def saudi_phone(self): """Generate Saudi mobile number.""" prefix = random.choice(SAUDI_MOBILE_PREFIXES) number = ''.join([str(random.randint(0, 9)) for _ in range(7)]) return f'+966{prefix}{number}' def saudi_national_id(self): """Generate Saudi national ID (10 digits).""" first_digit = random.choice(['1', '2']) # 1 for Saudi, 2 for resident remaining = ''.join([str(random.randint(0, 9)) for _ in range(9)]) return f'{first_digit}{remaining}' def saudi_vat_number(self): """Generate Saudi VAT number (15 digits, starts and ends with 3).""" middle = ''.join([str(random.randint(0, 9)) for _ in range(13)]) return f'3{middle}3' def saudi_name(self, sex='M'): """Generate Saudi name in both English and Arabic with father and grandfather names.""" if sex == 'M': first_en = random.choice(SAUDI_MALE_NAMES_EN) first_ar = random.choice(SAUDI_MALE_NAMES_AR) else: first_en = random.choice(SAUDI_FEMALE_NAMES_EN) first_ar = random.choice(SAUDI_FEMALE_NAMES_AR) # Father and grandfather names are typically male names father_en = random.choice(SAUDI_MALE_NAMES_EN) father_ar = random.choice(SAUDI_MALE_NAMES_AR) grandfather_en = random.choice(SAUDI_MALE_NAMES_EN) grandfather_ar = random.choice(SAUDI_MALE_NAMES_AR) last_en = random.choice(SAUDI_FAMILY_NAMES_EN) last_ar = random.choice(SAUDI_FAMILY_NAMES_AR) return { 'first_en': first_en, 'first_ar': first_ar, 'father_en': father_en, 'father_ar': father_ar, 'grandfather_en': grandfather_en, 'grandfather_ar': grandfather_ar, 'last_en': last_en, 'last_ar': last_ar } def saudi_address(self): """Generate Saudi address.""" city = random.choice(SAUDI_CITIES) if city == 'Riyadh': district = random.choice(RIYADH_DISTRICTS) else: district = f'Al-{self.fake_en.word().title()}' street = f'{self.fake_en.street_name()}' building = random.randint(1, 9999) postal_code = ''.join([str(random.randint(0, 9)) for _ in range(5)]) return { 'address': f'Building {building}, {street}, {district}', 'city': city, 'postal_code': postal_code } class Command(BaseCommand): help = 'Generate Saudi-influenced test data for all apps' def add_arguments(self, parser): parser.add_argument( '--tenants', type=int, default=1, help='Number of tenants to create (default: 1)' ) parser.add_argument( '--patients', type=int, default=50, help='Number of patients per tenant (default: 50)' ) parser.add_argument( '--appointments', type=int, default=100, help='Number of appointments per tenant (default: 100)' ) parser.add_argument( '--clear', action='store_true', help='Clear existing data before generating new data' ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.generator = SaudiDataGenerator() self.created_counts = {} def handle(self, *args, **options): self.stdout.write(self.style.SUCCESS('Starting Saudi-influenced test data generation...')) if options['clear']: self.stdout.write(self.style.WARNING('Clearing existing data...')) self.clear_data() try: with transaction.atomic(): # Create superuser if it doesn't exist self.create_superuser() # Generate setting templates first (global, not tenant-specific) self.generate_setting_templates() # Generate data in order of dependencies tenants = self.generate_tenants(options['tenants']) for tenant in tenants: self.stdout.write(f'\nGenerating data for tenant: {tenant.name}') # Generate tenant settings self.generate_tenant_settings(tenant) # Generate consent templates self.generate_consent_templates(tenant) # Foundation layer users = self.generate_users(tenant) clinics = self.generate_clinics(tenant) # Patient layer patients = self.generate_patients(tenant, options['patients']) # Scheduling layer providers = self.generate_providers(tenant, users, clinics) rooms = self.generate_rooms(tenant, clinics) schedules = self.generate_schedules(providers) appointments = self.generate_appointments( tenant, patients, providers, clinics, rooms, options['appointments'] ) # Clinical layer self.generate_clinical_data(tenant, patients, appointments, providers, users) # Financial layer services = self.generate_services(tenant, clinics) packages = self.generate_packages(tenant, services) self.generate_financial_data(tenant, patients, appointments, services, packages) # Communication layer self.generate_communication_data(tenant, patients, appointments) # Integration layer self.generate_integration_data(tenant, patients, appointments, clinics, users) # HR layer self.generate_hr_data(tenant, users) # Documents layer self.generate_documents_data(tenant, patients, appointments, users) # MDT layer self.generate_mdt_data(tenant, patients, users, clinics) self.print_summary() self.stdout.write(self.style.SUCCESS('\n✓ Test data generation completed successfully!')) except Exception as e: self.stdout.write(self.style.ERROR(f'\n✗ Error generating data: {str(e)}')) raise def create_superuser(self): """Create superuser if it doesn't exist.""" username = 'admin' email = 'marwan@tenhal.sa' if User.objects.filter(username=username).exists(): self.stdout.write(f' Superuser "{username}" already exists, skipping...') return superuser = User.objects.create_superuser( username=username, email=email, password='Kfsh&rc9788', first_name='Marwan', last_name='Alwali', phone_number='+966501234567', timezone='Asia/Riyadh', email_verified=True, preferences={ 'language': 'en', 'notifications_enabled': True, 'theme': 'light' } ) self.stdout.write(self.style.SUCCESS(f' ✓ Created superuser: {username} ({email})')) self.created_counts['superuser'] = 1 def clear_data(self): """Clear all existing data (except superusers).""" # Import OT models for clearing from ot.models import OTDifficultyArea, OTMilestone, OTSelfHelpSkill, OTInfantBehavior, OTCurrentBehavior, OTScoringConfig models_to_clear = [ # Clear in reverse dependency order NoteAuditLog, NoteAddendum, ClinicalNote, DocumentTemplate, SLPProgressReport, SLPTarget, SLPIntervention, SLPAssessment, SLPConsult, OTProgressReport, OTTargetSkill, OTSession, # Clear OT related models before OTConsult OTDifficultyArea, OTMilestone, OTSelfHelpSkill, OTInfantBehavior, OTCurrentBehavior, OTConsult, OTScoringConfig, ABASkillTarget, ABASession, ABAGoal, ABABehavior, ABAConsult, VitalSignsAlert, GrowthChart, NursingEncounter, ConsultationFeedback, ConsultationResponse, MedicalFollowUp, MedicationPlan, MedicalConsultation, PayerContract, ZatcaCredential, EInvoice, NphiesEncounterLink, NphiesMessage, ExternalOrder, Referral, MessageLog, Message, NotificationPreference, MessageTemplate, CSID, PackagePurchase, Payment, InvoiceLineItem, Invoice, Payer, Package, Service, AppointmentConfirmation, AppointmentReminder, Appointment, Schedule, Room, Provider, AuditLog, Attachment, Consent, SubFile, File, Patient, Clinic, TenantSetting, ] for model in models_to_clear: count = model.objects.count() model.objects.all().delete() self.stdout.write(f' Cleared {count} {model.__name__} records') # Clear non-superuser users and tenants User.objects.filter(is_superuser=False).delete() Tenant.objects.all().delete() # Don't clear SettingTemplate as it's global configuration def generate_setting_templates(self): """Generate setting templates (global configuration).""" if SettingTemplate.objects.exists(): self.stdout.write(' Setting templates already exist, skipping...') return templates = [ # Basic Information { 'key': 'basic_clinic_name_en', 'category': SettingTemplate.Category.BASIC, 'label_en': 'Clinic Name (English)', 'label_ar': 'اسم العيادة (إنجليزي)', 'data_type': SettingTemplate.DataType.STRING, 'is_required': True, 'order': 1, }, { 'key': 'basic_clinic_name_ar', 'category': SettingTemplate.Category.BASIC, 'label_en': 'Clinic Name (Arabic)', 'label_ar': 'اسم العيادة (عربي)', 'data_type': SettingTemplate.DataType.STRING, 'is_required': True, 'order': 2, }, # VAT Registration { 'key': 'vat_registration_number', 'category': SettingTemplate.Category.VAT, 'label_en': 'VAT Registration Number', 'label_ar': 'رقم التسجيل الضريبي', 'data_type': SettingTemplate.DataType.STRING, 'is_required': True, 'validation_regex': r'^3\d{13}3$', 'help_text_en': '15 digits, must start and end with 3', 'help_text_ar': '15 رقم، يجب أن يبدأ وينتهي بـ 3', 'order': 1, }, # Address Information { 'key': 'address_street', 'category': SettingTemplate.Category.ADDRESS, 'label_en': 'Street Address', 'label_ar': 'عنوان الشارع', 'data_type': SettingTemplate.DataType.TEXT, 'is_required': True, 'order': 1, }, { 'key': 'address_city', 'category': SettingTemplate.Category.ADDRESS, 'label_en': 'City', 'label_ar': 'المدينة', 'data_type': SettingTemplate.DataType.STRING, 'is_required': True, 'default_value': 'Riyadh', 'order': 2, }, { 'key': 'address_postal_code', 'category': SettingTemplate.Category.ADDRESS, 'label_en': 'Postal Code', 'label_ar': 'الرمز البريدي', 'data_type': SettingTemplate.DataType.STRING, 'is_required': True, 'order': 3, }, ] for template_data in templates: SettingTemplate.objects.create(**template_data) self.stdout.write(f' Created {len(templates)} setting templates') def generate_tenant_settings(self, tenant): """Generate tenant-specific settings.""" templates = SettingTemplate.objects.filter(is_active=True) for template in templates: # Generate appropriate value based on template if template.key == 'basic_clinic_name_en': value = tenant.name elif template.key == 'basic_clinic_name_ar': value = tenant.settings.get('name_ar', '') elif template.key == 'vat_registration_number': value = tenant.vat_number elif template.key == 'address_street': value = tenant.address elif template.key == 'address_city': value = tenant.city elif template.key == 'address_postal_code': value = tenant.postal_code else: value = template.default_value TenantSetting.objects.create( tenant=tenant, template=template, value=value ) self.stdout.write(f' Created {templates.count()} tenant settings') def generate_consent_templates(self, tenant): """Generate consent templates for different consent types.""" templates_data = [ { 'consent_type': ConsentTemplate.ConsentType.GENERAL_TREATMENT, 'title_en': 'General Treatment Consent', 'title_ar': 'موافقة العلاج العامة', 'content_en': '''GENERAL TREATMENT CONSENT FORM Patient Name: {patient_name} Medical Record Number: {patient_mrn} Date of Birth: {patient_dob} Age: {patient_age} Date: {date} I, the undersigned, hereby consent to receive medical treatment and therapeutic services at {clinic_name}. I understand that: 1. The treatment plan will be explained to me by the healthcare provider 2. I have the right to ask questions about my treatment 3. I can refuse treatment at any time 4. My medical information will be kept confidential 5. Treatment may involve some risks and side effects I acknowledge that I have read and understood this consent form, and I voluntarily agree to the proposed treatment. Signature: ___________________________ Name: {patient_name} Date: {date}''', 'content_ar': '''نموذج موافقة العلاج العامة اسم المريض: {patient_name} رقم السجل الطبي: {patient_mrn} تاريخ الميلاد: {patient_dob} العمر: {patient_age} التاريخ: {date} أنا الموقع أدناه، أوافق بموجب هذا على تلقي العلاج الطبي والخدمات العلاجية في {clinic_name}. أفهم أن: ١. سيتم شرح خطة العلاج لي من قبل مقدم الرعاية الصحية ٢. لدي الحق في طرح الأسئلة حول علاجي ٣. يمكنني رفض العلاج في أي وقت ٤. ستبقى معلوماتي الطبية سرية ٥. قد ينطوي العلاج على بعض المخاطر والآثار الجانبية أقر بأنني قرأت وفهمت نموذج الموافقة هذا، وأوافق طوعاً على العلاج المقترح. التوقيع: ___________________________ الاسم: {patient_name} التاريخ: {date}''', 'version': 1 }, { 'consent_type': ConsentTemplate.ConsentType.SERVICE_SPECIFIC, 'title_en': 'Service-Specific Consent', 'title_ar': 'موافقة خاصة بالخدمة', 'content_en': '''SERVICE-SPECIFIC CONSENT FORM Patient Name: {patient_name} Medical Record Number: {patient_mrn} Date: {date} I consent to receive the following specific services: - Occupational Therapy - Speech-Language Therapy - ABA Therapy - Other therapeutic interventions as recommended I understand that: 1. Each service has specific goals and objectives 2. Progress will be monitored and reported regularly 3. Services may be adjusted based on patient progress 4. I will be informed of any changes to the treatment plan I give my consent for the healthcare team to provide these services. Signature: ___________________________ Name: {patient_name} Date: {date}''', 'content_ar': '''نموذج موافقة خاصة بالخدمة اسم المريض: {patient_name} رقم السجل الطبي: {patient_mrn} التاريخ: {date} أوافق على تلقي الخدمات المحددة التالية: - العلاج الوظيفي - علاج النطق واللغة - علاج ABA - تدخلات علاجية أخرى حسب التوصية أفهم أن: ١. كل خدمة لها أهداف وغايات محددة ٢. سيتم مراقبة التقدم والإبلاغ عنه بانتظام ٣. قد يتم تعديل الخدمات بناءً على تقدم المريض ٤. سأبلغ بأي تغييرات في خطة العلاج أعطي موافقتي لفريق الرعاية الصحية لتقديم هذه الخدمات. التوقيع: ___________________________ الاسم: {patient_name} التاريخ: {date}''', 'version': 1 }, { 'consent_type': ConsentTemplate.ConsentType.PHOTO_VIDEO, 'title_en': 'Photo/Video Consent', 'title_ar': 'موافقة التصوير الفوتوغرافي/الفيديو', 'content_en': '''PHOTO/VIDEO CONSENT FORM Patient Name: {patient_name} Medical Record Number: {patient_mrn} Date: {date} I consent to the use of photographs and/or video recordings of the patient for: ☐ Medical documentation and treatment planning ☐ Educational purposes (training healthcare professionals) ☐ Research purposes (with patient identity protected) ☐ Marketing and promotional materials (with patient identity protected) I understand that: 1. Photos/videos will be stored securely 2. Patient identity will be protected when used for educational/research purposes 3. I can withdraw this consent at any time 4. Photos/videos will not be shared without proper authorization Signature: ___________________________ Name: {patient_name} Date: {date}''', 'content_ar': '''نموذج موافقة التصوير الفوتوغرافي/الفيديو اسم المريض: {patient_name} رقم السجل الطبي: {patient_mrn} التاريخ: {date} أوافق على استخدام الصور الفوتوغرافية و/أو تسجيلات الفيديو للمريض لـ: ☐ التوثيق الطبي وتخطيط العلاج ☐ الأغراض التعليمية (تدريب المهنيين الصحيين) ☐ أغراض البحث (مع حماية هوية المريض) ☐ المواد التسويقية والترويجية (مع حماية هوية المريض) أفهم أن: ١. سيتم تخزين الصور/الفيديوهات بشكل آمن ٢. ستتم حماية هوية المريض عند استخدامها للأغراض التعليمية/البحثية ٣. يمكنني سحب هذه الموافقة في أي وقت ٤. لن تتم مشاركة الصور/الفيديوهات بدون تصريح مناسب التوقيع: ___________________________ الاسم: {patient_name} التاريخ: {date}''', 'version': 1 }, { 'consent_type': ConsentTemplate.ConsentType.DATA_SHARING, 'title_en': 'Data Sharing Consent', 'title_ar': 'موافقة مشاركة البيانات', 'content_en': '''DATA SHARING CONSENT FORM Patient Name: {patient_name} Medical Record Number: {patient_mrn} Date: {date} I consent to the sharing of patient medical information with: ☐ Other healthcare providers involved in patient care ☐ Insurance companies for billing purposes ☐ Educational institutions (with patient identity protected) ☐ Research institutions (with patient identity protected) I understand that: 1. Data will be shared only for legitimate healthcare purposes 2. Patient confidentiality will be maintained 3. Data will be shared in compliance with privacy regulations 4. I can limit or withdraw this consent at any time Signature: ___________________________ Name: {patient_name} Date: {date}''', 'content_ar': '''نموذج موافقة مشاركة البيانات اسم المريض: {patient_name} رقم السجل الطبي: {patient_mrn} التاريخ: {date} أوافق على مشاركة المعلومات الطبية للمريض مع: ☐ مقدمي الرعاية الصحية الآخرين المشاركين في رعاية المريض ☐ شركات التأمين لأغراض الفوترة ☐ المؤسسات التعليمية (مع حماية هوية المريض) ☐ مؤسسات البحث (مع حماية هوية المريض) أفهم أن: ١. سيتم مشاركة البيانات فقط لأغراض الرعاية الصحية المشروعة ٢. سيتم الحفاظ على سرية المريض ٣. سيتم مشاركة البيانات وفقاً لأنظمة الخصوصية ٤. يمكنني تقييد أو سحب هذه الموافقة في أي وقت التوقيع: ___________________________ الاسم: {patient_name} التاريخ: {date}''', 'version': 1 } ] templates = [] for template_data in templates_data: template = ConsentTemplate.objects.create( tenant=tenant, **template_data ) templates.append(template) self.created_counts.setdefault('consent_templates', 0) self.created_counts['consent_templates'] += len(templates) self.stdout.write(f' Created {len(templates)} consent templates') return templates def generate_tenants(self, count): """Generate tenant organizations.""" tenants = [] tenant_names = [ ('Agdar Center', 'مركز أقدر', 'AGDAR'), ('Al-Noor Medical Center', 'مركز النور الطبي', 'ALNOOR'), ('Shifa Healthcare', 'شفاء للرعاية الصحية', 'SHIFA'), ] for i in range(min(count, len(tenant_names))): name_en, name_ar, code = tenant_names[i] address_data = self.generator.saudi_address() tenant = Tenant.objects.create( name=name_en, code=code, vat_number=self.generator.saudi_vat_number(), address=address_data['address'], city=address_data['city'], postal_code=address_data['postal_code'], country_code='SA', is_active=True, settings={ 'name_ar': name_ar, 'currency': 'SAR', 'timezone': 'Asia/Riyadh', 'language': 'ar' } ) tenants.append(tenant) self.stdout.write(f' Created tenant: {tenant.name}') self.created_counts['tenants'] = len(tenants) return tenants def generate_users(self, tenant): """Generate users with Saudi names.""" users = [] roles = [ (User.Role.ADMIN, 2), (User.Role.DOCTOR, 3), (User.Role.NURSE, 3), (User.Role.OT, 2), (User.Role.SLP, 2), (User.Role.ABA, 2), (User.Role.FRONT_DESK, 2), (User.Role.FINANCE, 2), ] for role, count in roles: for i in range(count): sex = random.choice(['M', 'F']) name = self.generator.saudi_name(sex) username = f"{name['first_en'].lower()}.{name['last_en'].lower()}.{role.lower()}{i+1}" user = User.objects.create_user( username=username, email=f"{username}@{tenant.code.lower()}.sa", password='test123', tenant=tenant, role=role, first_name=name['first_en'], last_name=name['last_en'], phone_number=self.generator.saudi_phone(), employee_id=f"E{random.randint(1000, 9999)}", bio=f"Healthcare professional specializing in {role}", timezone='Asia/Riyadh', email_verified=random.choice([True, False]), preferences={ 'language': random.choice(['en', 'ar']), 'notifications_enabled': True, 'theme': 'light' } ) users.append(user) self.created_counts.setdefault('users', 0) self.created_counts['users'] += len(users) self.stdout.write(f' Created {len(users)} users') return users def generate_clinics(self, tenant): """Generate clinics/departments.""" clinics_data = [ ('Medical Department', 'القسم الطبي', Clinic.Specialty.MEDICAL, 'MED'), ('Nursing Department', 'قسم التمريض', Clinic.Specialty.NURSING, 'NUR'), ('ABA Therapy', 'علاج ABA', Clinic.Specialty.ABA, 'ABA'), ('Occupational Therapy', 'العلاج الوظيفي', Clinic.Specialty.OT, 'OT'), ('Speech-Language Pathology', 'علاج النطق واللغة', Clinic.Specialty.SLP, 'SLP'), ] clinics = [] for name_en, name_ar, specialty, code in clinics_data: clinic = Clinic.objects.create( tenant=tenant, name_en=name_en, name_ar=name_ar, specialty=specialty, code=code, is_active=True ) clinics.append(clinic) self.created_counts.setdefault('clinics', 0) self.created_counts['clinics'] += len(clinics) self.stdout.write(f' Created {len(clinics)} clinics') return clinics def generate_patients(self, tenant, count): """Generate patients with Saudi demographics.""" patients = [] for i in range(count): sex = random.choice(['M', 'F']) name = self.generator.saudi_name(sex) address = self.generator.saudi_address() # Generate realistic age distribution (more children for therapy center) age_years = random.choices( [random.randint(2, 12), random.randint(13, 18), random.randint(19, 60)], weights=[60, 25, 15] )[0] dob = date.today() - timedelta(days=age_years * 365 + random.randint(0, 364)) # Generate MRN mrn = f"{str(i+1).zfill(6)}" patient = Patient.objects.create( tenant=tenant, mrn=mrn, national_id=self.generator.saudi_national_id(), first_name_en=name['first_en'], father_name_en=name['father_en'], grandfather_name_en=name['grandfather_en'], last_name_en=name['last_en'], first_name_ar=name['first_ar'], father_name_ar=name['father_ar'], grandfather_name_ar=name['grandfather_ar'], last_name_ar=name['last_ar'], date_of_birth=dob, sex=sex, phone=self.generator.saudi_phone(), email=f"{name['first_en'].lower()}.{name['last_en'].lower()}@example.sa", caregiver_name=f"{random.choice(SAUDI_MALE_NAMES_EN if sex == 'M' else SAUDI_FEMALE_NAMES_EN)} {name['last_en']}", caregiver_phone=self.generator.saudi_phone(), caregiver_relationship=random.choice(['Father', 'Mother', 'Guardian']), address=address['address'], city=address['city'], postal_code=address['postal_code'], emergency_contact=f"Emergency: {self.generator.saudi_phone()}" ) # Create notification preferences NotificationPreference.objects.create( patient=patient, sms_enabled=True, whatsapp_enabled=random.choice([True, False]), email_enabled=random.choice([True, False]), preferred_language=random.choice(['en', 'ar']), preferred_channel=random.choice([ Message.Channel.SMS, Message.Channel.WHATSAPP ]) ) # Note: File is automatically created by signal, no need to create it here patients.append(patient) self.created_counts.setdefault('patients', 0) self.created_counts['patients'] += len(patients) self.stdout.write(f' Created {len(patients)} patients') return patients def generate_providers(self, tenant, users, clinics): """Generate provider profiles.""" providers = [] clinical_users = [u for u in users if u.role in [ User.Role.DOCTOR, User.Role.NURSE, User.Role.OT, User.Role.SLP, User.Role.ABA ]] for user in clinical_users: # Map user role to clinic specialty specialty_map = { User.Role.DOCTOR: Clinic.Specialty.MEDICAL, User.Role.NURSE: Clinic.Specialty.NURSING, User.Role.OT: Clinic.Specialty.OT, User.Role.SLP: Clinic.Specialty.SLP, User.Role.ABA: Clinic.Specialty.ABA, } specialty = specialty_map.get(user.role) relevant_clinics = [c for c in clinics if c.specialty == specialty] provider = Provider.objects.create( tenant=tenant, user=user, is_available=True, max_daily_appointments=random.randint(15, 25) ) if relevant_clinics: provider.specialties.set(relevant_clinics) providers.append(provider) self.created_counts.setdefault('providers', 0) self.created_counts['providers'] += len(providers) self.stdout.write(f' Created {len(providers)} providers') return providers def generate_rooms(self, tenant, clinics): """Generate rooms for each clinic.""" rooms = [] for clinic in clinics: for i in range(random.randint(2, 4)): room = Room.objects.create( tenant=tenant, name=f"{clinic.name_en} Room {i+1}", room_number=f"{clinic.code}-{i+1:02d}", clinic=clinic, is_available=True ) rooms.append(room) self.created_counts.setdefault('rooms', 0) self.created_counts['rooms'] += len(rooms) self.stdout.write(f' Created {len(rooms)} rooms') return rooms def generate_schedules(self, providers): """Generate weekly schedules for providers (Sunday-Thursday).""" schedules = [] for provider in providers: # Saudi work week: Sunday (0) to Thursday (4) for day in range(5): # 0-4 (Sunday to Thursday) # Morning shift schedule = Schedule.objects.create( provider=provider, day_of_week=day, start_time='08:00', end_time='12:00', slot_duration=30, is_active=True ) schedules.append(schedule) # Afternoon shift (after prayer break) schedule = Schedule.objects.create( provider=provider, day_of_week=day, start_time='15:00', end_time='19:00', slot_duration=30, is_active=True ) schedules.append(schedule) self.created_counts.setdefault('schedules', 0) self.created_counts['schedules'] += len(schedules) self.stdout.write(f' Created {len(schedules)} schedules') return schedules def generate_appointments(self, tenant, patients, providers, clinics, rooms, count): """Generate appointments with varied statuses.""" appointments = [] # Generate appointments over the past 3 months and next month start_date = date.today() - timedelta(days=90) end_date = date.today() + timedelta(days=30) for i in range(count): patient = random.choice(patients) provider = random.choice(providers) clinic = random.choice(list(provider.specialties.all())) if provider.specialties.exists() else random.choice(clinics) room = random.choice([r for r in rooms if r.clinic == clinic]) if rooms else None # Generate appointment date (weighted towards recent dates) days_offset = random.choices( range((end_date - start_date).days), weights=[1.5 if i < 90 else 0.5 for i in range((end_date - start_date).days)] )[0] appt_date = start_date + timedelta(days=days_offset) # Skip Fridays and Saturdays (Saudi weekend) while appt_date.weekday() in [4, 5]: # Friday=4, Saturday=5 appt_date += timedelta(days=1) # Generate time slot hour = random.choice([8, 9, 10, 11, 15, 16, 17, 18]) minute = random.choice([0, 30]) appt_time = f"{hour:02d}:{minute:02d}" # Determine status based on date if appt_date < date.today(): status = random.choices( [Appointment.Status.COMPLETED, Appointment.Status.NO_SHOW, Appointment.Status.CANCELLED], weights=[75, 15, 10] )[0] elif appt_date == date.today(): status = random.choices( [Appointment.Status.CONFIRMED, Appointment.Status.ARRIVED, Appointment.Status.IN_PROGRESS], weights=[40, 35, 25] )[0] else: status = random.choices( [Appointment.Status.BOOKED, Appointment.Status.CONFIRMED], weights=[30, 70] )[0] appointment = Appointment.objects.create( tenant=tenant, appointment_number=f"A{str(i+1).zfill(6)}", patient=patient, clinic=clinic, provider=provider, room=room, service_type=random.choice([ 'Initial Consultation', 'Follow-up', 'Therapy Session', 'Assessment', 'Progress Review' ]), scheduled_date=appt_date, scheduled_time=appt_time, duration=random.choice([30, 45, 60]), status=status, finance_cleared=random.choice([True, False]) if status != Appointment.Status.CANCELLED else False, consent_verified=random.choice([True, False]) if status != Appointment.Status.CANCELLED else False, notes=random.choice(['', 'First visit', 'Regular session', 'Parent requested']) ) # Add timestamps for completed appointments if status in [Appointment.Status.COMPLETED, Appointment.Status.NO_SHOW]: appt_datetime = datetime.combine(appt_date, datetime.strptime(appt_time, '%H:%M').time()) appointment.arrival_at = timezone.make_aware(appt_datetime) if status == Appointment.Status.COMPLETED: appointment.start_at = appointment.arrival_at + timedelta(minutes=5) appointment.end_at = appointment.start_at + timedelta(minutes=appointment.duration) appointment.save() appointments.append(appointment) self.created_counts.setdefault('appointments', 0) self.created_counts['appointments'] += len(appointments) self.stdout.write(f' Created {len(appointments)} appointments') return appointments def generate_clinical_data(self, tenant, patients, appointments, providers, users): """Generate clinical records for various specialties.""" # Sample of completed appointments for clinical data completed_appointments = [a for a in appointments if a.status == Appointment.Status.COMPLETED] if not completed_appointments: return # Generate medical consultations and related data medical_providers = [p for p in providers if p.user.role == User.Role.DOCTOR] if medical_providers: consultations = [] for _ in range(min(20, len(completed_appointments))): appt = random.choice(completed_appointments) if not hasattr(appt, 'medical_consultations') or not appt.medical_consultations.exists(): consultation = MedicalConsultation.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, consultation_date=appt.scheduled_date, provider=random.choice(medical_providers).user, chief_complaint=random.choice([ 'Developmental delay concerns', 'Speech and language difficulties', 'Behavioral issues', 'Motor skill challenges' ]), clinical_summary='Patient evaluated and treatment plan discussed.', recommendations='Continue therapy sessions as scheduled.' ) consultations.append(consultation) # Generate medication plans for some consultations for consultation in random.sample(consultations, min(10, len(consultations))): MedicationPlan.objects.create( consultation=consultation, drug_name=random.choice(['Risperidone', 'Melatonin', 'Omega-3', 'Vitamin D']), dose=random.choice(['2.5mg', '5mg', '10mg', '500mg']), frequency=random.choice(list(MedicationPlan.Frequency.choices))[0], compliance=random.choice(list(MedicationPlan.Compliance.choices))[0], gains='Patient showing improvement', side_effects='', target_behavior='', improved=random.choice([True, False]) ) # Generate consultation responses from other disciplines for consultation in random.sample(consultations, min(8, len(consultations))): response_type = random.choice(list(ConsultationResponse.ResponseType.choices))[0] # Find a provider of the appropriate type responder_role_map = { 'OT': User.Role.OT, 'SLP': User.Role.SLP, 'ABA': User.Role.ABA, 'NURSING': User.Role.NURSE, } responder_role = responder_role_map.get(response_type, User.Role.DOCTOR) potential_responders = [u for u in users if u.role == responder_role] if potential_responders: ConsultationResponse.objects.create( tenant=tenant, consultation=consultation, response_type=response_type, responder=random.choice(potential_responders), response_date=consultation.consultation_date + timedelta(days=random.randint(1, 7)), assessment='Patient evaluated from ' + response_type + ' perspective', recommendations='Continue coordinated care', follow_up_needed=random.choice([True, False]) ) # Generate consultation feedback for consultation in random.sample(consultations, min(12, len(consultations))): feedback_type = random.choice(list(ConsultationFeedback.FeedbackType.choices))[0] if feedback_type == 'FAMILY': ConsultationFeedback.objects.create( consultation=consultation, feedback_type=feedback_type, submitted_by_name=consultation.patient.caregiver_name or 'Family Member', feedback_date=consultation.consultation_date + timedelta(days=random.randint(1, 14)), satisfaction_rating=random.randint(3, 5), communication_rating=random.randint(3, 5), care_quality_rating=random.randint(3, 5), comments='We are satisfied with the care provided', concerns='', suggestions='' ) else: ConsultationFeedback.objects.create( consultation=consultation, feedback_type=feedback_type, submitted_by=random.choice(users), feedback_date=consultation.consultation_date + timedelta(days=random.randint(1, 7)), satisfaction_rating=random.randint(3, 5), communication_rating=random.randint(3, 5), care_quality_rating=random.randint(3, 5), comments='Good interdisciplinary collaboration', concerns='', suggestions='' ) # Generate follow-ups for some consultations for consultation in random.sample(consultations, min(8, len(consultations))): follow_up_date = consultation.consultation_date + timedelta(days=random.randint(14, 60)) # Create previous complaints status previous_complaints_status = {} if consultation.chief_complaint: complaints = consultation.chief_complaint.split(',')[:3] for complaint in complaints: previous_complaints_status[complaint.strip()] = random.choice(['RESOLVED', 'STATIC', 'WORSE']) MedicalFollowUp.objects.create( tenant=tenant, patient=consultation.patient, previous_consultation=consultation, followup_date=follow_up_date, previous_complaints_status=previous_complaints_status, new_complaints='Follow-up assessment', assessment='Patient progress reviewed', recommendations='Continue current treatment plan', provider=consultation.provider, family_satisfaction=random.choice(list(MedicalFollowUp.FamilySatisfaction.choices))[0] ) self.created_counts.setdefault('medical_consultations', 0) self.created_counts['medical_consultations'] += len(consultations) # Generate nursing encounters and related data nursing_providers = [p for p in providers if p.user.role == User.Role.NURSE] if nursing_providers: encounters = [] for _ in range(min(30, len(completed_appointments))): appt = random.choice(completed_appointments) encounter = NursingEncounter.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, encounter_date=appt.scheduled_date, filled_by=random.choice(nursing_providers).user, height_cm=Decimal(str(random.uniform(80, 180))), weight_kg=Decimal(str(random.uniform(15, 80))), hr_bpm=random.randint(60, 100), bp_systolic=random.randint(90, 130), bp_diastolic=random.randint(60, 85), temperature=Decimal(str(random.uniform(36.5, 37.5))), spo2=random.randint(95, 100) ) encounters.append(encounter) # Generate growth charts for pediatric patients pediatric_patients = [p for p in patients if p.age <= 18] for patient in random.sample(pediatric_patients, min(15, len(pediatric_patients))): patient_encounters = [e for e in encounters if e.patient == patient] if patient_encounters: encounter = random.choice(patient_encounters) # Calculate age in months from dateutil.relativedelta import relativedelta age_delta = relativedelta(encounter.encounter_date, patient.date_of_birth) age_months = age_delta.years * 12 + age_delta.months GrowthChart.objects.create( patient=patient, nursing_encounter=encounter, measurement_date=encounter.encounter_date, age_months=age_months, height_cm=encounter.height_cm, weight_kg=encounter.weight_kg, head_circumference_cm=Decimal(str(random.uniform(40, 55))) if patient.age <= 5 else None, percentile_height=Decimal(str(random.uniform(10, 90))), percentile_weight=Decimal(str(random.uniform(10, 90))) ) # Generate vital signs alerts for abnormal readings for encounter in random.sample(encounters, min(5, len(encounters))): if encounter.hr_bpm and encounter.hr_bpm > 100: VitalSignsAlert.objects.create( tenant=tenant, nursing_encounter=encounter, vital_sign='Heart Rate', value=f'{encounter.hr_bpm} bpm', severity=random.choice(list(VitalSignsAlert.Severity.choices))[0], status=random.choice(list(VitalSignsAlert.Status.choices))[0], acknowledged_by=random.choice(nursing_providers).user if random.choice([True, False]) else None, notes='Elevated heart rate detected' ) elif encounter.bp_systolic and encounter.bp_systolic > 130: VitalSignsAlert.objects.create( tenant=tenant, nursing_encounter=encounter, vital_sign='Blood Pressure', value=f'{encounter.bp_systolic}/{encounter.bp_diastolic} mmHg', severity=random.choice(list(VitalSignsAlert.Severity.choices))[0], status=random.choice(list(VitalSignsAlert.Status.choices))[0], acknowledged_by=random.choice(nursing_providers).user if random.choice([True, False]) else None, notes='Elevated blood pressure detected' ) self.created_counts.setdefault('nursing_encounters', 0) self.created_counts['nursing_encounters'] += len(encounters) # Generate ABA consultations and related data aba_providers = [p for p in providers if p.user.role == User.Role.ABA] if aba_providers: consults = [] for _ in range(min(15, len(completed_appointments))): appt = random.choice(completed_appointments) if appt.patient.age <= 18: # ABA typically for children consult = ABAConsult.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, consultation_date=appt.scheduled_date, provider=random.choice(aba_providers).user, reason_of_referral=random.choice(list(ABAConsult.ReasonOfReferral.choices))[0], parental_concern='Concerns about behavioral development', recommendations='Continue ABA therapy sessions' ) consults.append(consult) # Generate behaviors for ABA consults behavior_types = ['Aggression', 'Self-injury', 'Tantrums', 'Non-compliance', 'Stereotypy'] for consult in consults: for i in range(random.randint(1, 3)): ABABehavior.objects.create( consult=consult, behavior_description=f'Observed {random.choice(behavior_types).lower()} behavior', frequency=random.choice(list(ABABehavior.Frequency.choices))[0], duration=random.choice(['5-10 minutes', '10-30 minutes', '30+ minutes']), intensity=random.choice(list(ABABehavior.Intensity.choices))[0], antecedents_likely='Various triggers observed', antecedents_least_likely='Structured activities', consequences='Intervention applied', order=i ) # Generate goals for ABA consults goal_descriptions = [ 'Improve communication skills', 'Enhance social interaction', 'Develop self-care abilities', 'Increase academic engagement', 'Improve play skills' ] for consult in consults: for _ in range(random.randint(1, 2)): ABAGoal.objects.create( consult=consult, goal_description=random.choice(goal_descriptions), target_date=consult.consultation_date + timedelta(days=random.randint(30, 90)), status=random.choice(list(ABAGoal.Status.choices))[0], progress_notes='Progress being monitored' ) # Generate ABA sessions sessions = [] for _ in range(min(20, len(completed_appointments))): appt = random.choice(completed_appointments) if appt.patient.age <= 18: # ABA typically for children session = ABASession.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, session_date=appt.scheduled_date, provider=random.choice(aba_providers).user, session_type=random.choice(list(ABASession.SessionType.choices))[0], duration_minutes=random.choice([30, 45, 60]), engagement_level=random.randint(1, 5), cooperation_level=random.randint(1, 5), target_behaviors='Reduce tantrums, Increase eye contact, Improve turn-taking', interventions_used='Positive reinforcement, Visual schedules, Token economy', activities_performed='Structured play activities, social skills practice', behavioral_observations='Patient showed good engagement during session', progress_notes='Patient making progress on targeted behaviors', recommendations='Continue current intervention strategies', home_program='Practice turn-taking during family activities' ) sessions.append(session) # Generate skill targets for ABA sessions skill_names = [ 'Follows one-step directions', 'Makes eye contact when called', 'Shares toys with peers', 'Waits for turn', 'Uses words to request', 'Identifies emotions', 'Completes simple tasks', 'Responds to name' ] for session in sessions: for i in range(random.randint(2, 4)): trials_total = random.randint(5, 20) trials_correct = random.randint(0, trials_total) # Determine mastery level based on success rate success_rate = (trials_correct / trials_total * 100) if trials_total > 0 else 0 if success_rate == 0: mastery_level = 'NOT_STARTED' elif success_rate <= 25: mastery_level = 'EMERGING' elif success_rate <= 50: mastery_level = 'DEVELOPING' elif success_rate <= 75: mastery_level = 'PROGRESSING' else: mastery_level = 'MASTERED' ABASkillTarget.objects.create( session=session, skill_name=random.choice(skill_names), mastery_level=mastery_level, trials_correct=trials_correct, trials_total=trials_total, notes='Progress tracked during session', order=i ) self.created_counts.setdefault('aba_consults', 0) self.created_counts['aba_consults'] += len(consults) self.created_counts.setdefault('aba_sessions', 0) self.created_counts['aba_sessions'] += len(sessions) # Generate OT consultations and related data ot_providers = [p for p in providers if p.user.role == User.Role.OT] if ot_providers: from ot.scoring_service import initialize_consultation_data, OTScoringService from ot.models import OTDifficultyArea, OTMilestone, OTSelfHelpSkill, OTInfantBehavior, OTCurrentBehavior consults = [] sessions = [] # Generate OT consults with comprehensive data for _ in range(min(12, len(completed_appointments))): appt = random.choice(completed_appointments) provider_user = random.choice(ot_providers).user consult = OTConsult.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, consultation_date=appt.scheduled_date, provider=provider_user, referral_reason=random.choice(list(OTConsult.ReferralReason.choices))[0], motor_learning_difficulty=random.choice([True, False, None]), motor_learning_details='Some difficulty noted' if random.choice([True, False]) else '', motor_skill_regression=random.choice([True, False, None]), regression_details='Regression observed in some areas' if random.choice([True, False]) else '', eats_healthy_variety=random.choice([True, False, None]), eats_variety_textures=random.choice([True, False, None]), participates_family_meals=random.choice([True, False, None]), eating_comments='Eating patterns documented' if random.choice([True, False]) else '', infant_behavior_comments='Infant behavior patterns noted' if random.choice([True, False]) else '', current_behavior_comments='Current behavior patterns documented' if random.choice([True, False]) else '', recommendation=random.choice(list(OTConsult.Recommendation.choices))[0], recommendation_notes='Weekly OT sessions recommended', clinician_name=provider_user.get_full_name(), clinician_signature=provider_user.get_full_name() ) # Initialize all related data (milestones, skills, behaviors) initialize_consultation_data(consult) # Fill in some random data for difficulty areas (max 3) difficulty_areas_choices = ['sensory', 'fineMotor', 'grossMotor', 'oralMotor', 'adl', 'handwriting'] selected_areas = random.sample(difficulty_areas_choices, min(3, random.randint(1, 3))) for idx, area in enumerate(selected_areas): OTDifficultyArea.objects.create( consult=consult, area=area, details=f'Difficulty observed in {area}', order=idx ) # Fill in some milestone data (especially required ones) required_milestones = consult.milestones.filter(is_required=True) for milestone in required_milestones: milestone.age_achieved = f'{random.randint(6, 18)} months' milestone.save() # Fill in some random milestones other_milestones = consult.milestones.filter(is_required=False) for milestone in random.sample(list(other_milestones), min(5, len(other_milestones))): milestone.age_achieved = f'{random.randint(6, 36)} months' milestone.save() # Fill in self-help skills responses for skill in consult.self_help_skills.all(): if random.choice([True, False]): # 50% chance of answering skill.response = random.choice(['yes', 'no']) if skill.response == 'no' and random.choice([True, False]): skill.comments = 'Needs assistance with this skill' skill.save() # Fill in infant behaviors for behavior in consult.infant_behaviors.all(): if random.choice([True, False]): # 50% chance of answering behavior.response = random.choice(['yes', 'no', 'sometimes']) behavior.save() # Fill in current behaviors for behavior in consult.current_behaviors.all(): if random.choice([True, False]): # 50% chance of answering behavior.response = random.choice(['yes', 'no', 'sometimes']) behavior.save() # Calculate and save scores scoring_service = OTScoringService(consult) scoring_service.save_scores() consults.append(consult) # Generate OT sessions for _ in range(min(15, len(completed_appointments))): appt = random.choice(completed_appointments) session = OTSession.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, session_date=appt.scheduled_date, provider=random.choice(ot_providers).user, session_type=random.choice(list(OTSession.SessionType.choices))[0], cooperative_level=random.randint(1, 4), distraction_tolerance=random.randint(1, 4), observations='Patient showed good progress', recommendations='Continue current treatment plan' ) sessions.append(session) # Generate target skills for sessions skill_names = [ 'Pencil grasp', 'Scissor skills', 'Ball throwing', 'Balance beam walking', 'Sensory tolerance', 'Dressing skills', 'Feeding independence', 'Visual tracking' ] for session in sessions: for i in range(random.randint(1, 3)): OTTargetSkill.objects.create( session=session, skill_name=random.choice(skill_names), score=random.randint(0, 10), notes='Progress noted during session', order=i ) # Generate progress reports for patient in random.sample([s.patient for s in sessions], min(8, len(set([s.patient for s in sessions])))): patient_sessions = [s for s in sessions if s.patient == patient] if patient_sessions: OTProgressReport.objects.create( tenant=tenant, patient=patient, report_date=patient_sessions[-1].session_date, provider=patient_sessions[-1].provider, sessions_scheduled=len(patient_sessions) + random.randint(0, 2), sessions_attended=len(patient_sessions), goals_progress='Patient showing improvement in targeted skills', overall_progress='Patient has shown consistent progress', recommendations='Continue current treatment plan', continue_treatment=True ) self.created_counts.setdefault('ot_consults', 0) self.created_counts['ot_consults'] += len(consults) self.created_counts.setdefault('ot_sessions', 0) self.created_counts['ot_sessions'] += len(sessions) # Generate SLP consultations and related data slp_providers = [p for p in providers if p.user.role == User.Role.SLP] if slp_providers: consults = [] assessments = [] interventions = [] # Generate SLP consults for _ in range(min(12, len(completed_appointments))): appt = random.choice(completed_appointments) consult = SLPConsult.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, consultation_date=appt.scheduled_date, provider=random.choice(slp_providers).user, consult_variant=random.choice(list(SLPConsult.ConsultVariant.choices))[0], primary_concern='Speech and language delay concerns', suspected_areas='Articulation, language comprehension', type_of_service=random.choice(list(SLPConsult.ServiceType.choices))[0], recommendations='Speech therapy sessions recommended' ) consults.append(consult) # Generate assessments for consults for consult in consults: assessment = SLPAssessment.objects.create( tenant=tenant, patient=consult.patient, appointment=consult.appointment, assessment_date=consult.consultation_date, provider=consult.provider, diagnosis_statement='Speech and language disorder', case_history='Patient history documented', clinical_summary='Assessment findings documented', recommendations='Therapy plan developed' ) assessments.append(assessment) # Generate interventions for _ in range(min(15, len(completed_appointments))): appt = random.choice(completed_appointments) intervention = SLPIntervention.objects.create( tenant=tenant, patient=appt.patient, appointment=appt, session_number=random.randint(1, 20), session_date=appt.scheduled_date, session_time=appt.scheduled_time, provider=random.choice(slp_providers).user ) interventions.append(intervention) # Generate targets for interventions for intervention in interventions: for target_num in range(1, random.randint(2, 3)): # 1-2 targets SLPTarget.objects.create( intervention=intervention, target_number=target_num, subjective='Patient engaged during session', objective='Achieved 70% accuracy on target', assessment='Good progress noted', plan='Continue current approach', prompt_strategies='Visual cues, verbal prompts' ) # Generate progress reports for patient in random.sample([i.patient for i in interventions], min(8, len(set([i.patient for i in interventions])))): patient_interventions = [i for i in interventions if i.patient == patient] if patient_interventions: SLPProgressReport.objects.create( tenant=tenant, patient=patient, report_date=patient_interventions[-1].session_date, provider=patient_interventions[-1].provider, sessions_scheduled=len(patient_interventions) + random.randint(0, 2), sessions_attended=len(patient_interventions), final_diagnosis='Speech and language disorder', objectives_progress='Patient showing improvement in targeted areas', overall_progress='Patient has made progress in therapy', recommendations='Continue speech therapy sessions', package_sessions_count=random.choice([10, 20, 30]) ) self.created_counts.setdefault('slp_consults', 0) self.created_counts['slp_consults'] += len(consults) self.created_counts.setdefault('slp_interventions', 0) self.created_counts['slp_interventions'] += len(interventions) self.stdout.write(f' Created clinical records for all specialties') def generate_services(self, tenant, clinics): """Generate billable services.""" services = [] service_data = [ ('Initial Consultation', 'استشارة أولية', 300, 60), ('Follow-up Visit', 'زيارة متابعة', 200, 30), ('Therapy Session', 'جلسة علاجية', 250, 45), ('Assessment', 'تقييم', 400, 90), ('Progress Review', 'مراجعة التقدم', 150, 30), ] for clinic in clinics: for name_en, name_ar, price, duration in service_data: service = Service.objects.create( tenant=tenant, code=f"{clinic.code}-{name_en.replace(' ', '').upper()[:6]}", name_en=f"{clinic.name_en} - {name_en}", name_ar=f"{clinic.name_ar} - {name_ar}", clinic=clinic, base_price=price, duration_minutes=duration, is_active=True ) services.append(service) self.created_counts.setdefault('services', 0) self.created_counts['services'] += len(services) self.stdout.write(f' Created {len(services)} services') return services def generate_packages(self, tenant, services): """Generate service packages.""" packages = [] # Group services by clinic from itertools import groupby services_by_clinic = {} for service in sorted(services, key=lambda s: s.clinic.code): clinic_code = service.clinic.code if clinic_code not in services_by_clinic: services_by_clinic[clinic_code] = [] services_by_clinic[clinic_code].append(service) for clinic_code, clinic_services in services_by_clinic.items(): if len(clinic_services) >= 2: package = Package.objects.create( tenant=tenant, name_en=f"{clinic_services[0].clinic.name_en} - 10 Session Package", name_ar=f"{clinic_services[0].clinic.name_ar} - باقة 10 جلسات", total_sessions=10, price=2000, # Discounted from 2500 validity_days=90, is_active=True ) package.services.set(clinic_services[:2]) packages.append(package) self.created_counts.setdefault('packages', 0) self.created_counts['packages'] += len(packages) self.stdout.write(f' Created {len(packages)} packages') return packages def generate_financial_data(self, tenant, patients, appointments, services, packages): """Generate invoices, payments, CSID records, and package purchases.""" # Create payers for some patients payers = [] for patient in random.sample(patients, min(30, len(patients))): payer_type = random.choice([Payer.PayerType.SELF, Payer.PayerType.INSURANCE]) if payer_type == Payer.PayerType.INSURANCE: payer = Payer.objects.create( tenant=tenant, patient=patient, name=random.choice(SAUDI_INSURANCE_COMPANIES), payer_type=payer_type, policy_number=f"P{random.randint(100000, 999999)}", coverage_percentage=random.choice([50, 70, 80, 100]), is_active=True ) else: payer = Payer.objects.create( tenant=tenant, patient=patient, name='Self Pay', payer_type=payer_type, coverage_percentage=0, is_active=True ) payers.append(payer) # Create CSID records for tenant CSID.objects.create( tenant=tenant, csid_type=CSID.CSIDType.PRODUCTION, certificate='SAMPLE_CERTIFICATE_BASE64', secret='SAMPLE_SECRET', egs_serial_number=f'1-{tenant.code}|2-EGS|3-{random.randint(1000, 9999)}', common_name=f'{tenant.name} EGS Unit', organization_unit=tenant.name, issue_date=timezone.now() - timedelta(days=30), expiry_date=timezone.now() + timedelta(days=335), status=CSID.Status.ACTIVE ) # Create invoices for completed appointments completed_appointments = [a for a in appointments if a.status == Appointment.Status.COMPLETED] invoice_counter = 1 for appt in random.sample(completed_appointments, min(50, len(completed_appointments))): service = random.choice([s for s in services if s.clinic == appt.clinic]) # Determine invoice type based on patient nationality invoice_type = Invoice.InvoiceType.SIMPLIFIED if appt.patient.national_id.startswith('1') else Invoice.InvoiceType.STANDARD invoice = Invoice.objects.create( tenant=tenant, invoice_number=f"I{tenant.code}{random.randint(100000, 999999)}", invoice_counter=invoice_counter, invoice_type=invoice_type, patient=appt.patient, appointment=appt, issue_date=appt.scheduled_date, due_date=appt.scheduled_date + timedelta(days=30), subtotal=service.base_price, tax=service.base_price * Decimal('0.15') if not appt.patient.national_id.startswith('1') else Decimal('0'), total=service.base_price * Decimal('1.15') if not appt.patient.national_id.startswith('1') else service.base_price, status=random.choice([ Invoice.Status.PAID, Invoice.Status.ISSUED, Invoice.Status.PARTIALLY_PAID ]) ) invoice_counter += 1 # Create line item InvoiceLineItem.objects.create( invoice=invoice, service=service, description=service.name_en, quantity=1, unit_price=service.base_price, total=service.base_price ) # Create payment if paid if invoice.status in [Invoice.Status.PAID, Invoice.Status.PARTIALLY_PAID]: payment_amount = invoice.total if invoice.status == Invoice.Status.PAID else invoice.total * Decimal('0.5') Payment.objects.create( tenant=tenant, invoice=invoice, payment_date=timezone.now(), amount=payment_amount, method=random.choice(list(Payment.PaymentMethod.choices))[0], status=Payment.Status.COMPLETED, transaction_id=f"T{random.randint(100000, 999999)}", is_commission_free=random.choice([True, False]) # Random commission status ) # Create package purchases for some patients for patient in random.sample(patients, min(15, len(patients))): if packages: package = random.choice(packages) purchase_date = date.today() - timedelta(days=random.randint(0, 60)) sessions_used = random.randint(0, package.total_sessions) PackagePurchase.objects.create( tenant=tenant, patient=patient, package=package, purchase_date=purchase_date, expiry_date=purchase_date + timedelta(days=package.validity_days), total_sessions=package.total_sessions, sessions_used=sessions_used, status=random.choice(list(PackagePurchase.Status.choices))[0] ) self.stdout.write(f' Created financial records including CSID') def generate_communication_data(self, tenant, patients, appointments): """Generate message templates, messages, reminders, and confirmations.""" # Create message templates templates_data = [ ('appointment_reminder', 'Appointment Reminder', MessageTemplate.Channel.SMS, 'Dear {patient_name}, this is a reminder for your appointment on {appointment_date} at {appointment_time}.', 'عزيزي {patient_name}، هذا تذكير بموعدك في {appointment_date} الساعة {appointment_time}.'), ('appointment_confirmation', 'Appointment Confirmation', MessageTemplate.Channel.SMS, 'Your appointment has been confirmed for {appointment_date} at {appointment_time}.', 'تم تأكيد موعدك في {appointment_date} الساعة {appointment_time}.'), ] for code, name, channel, body_en, body_ar in templates_data: MessageTemplate.objects.create( tenant=tenant, code=code, name=name, channel=channel, body_en=body_en, body_ar=body_ar, variables=['patient_name', 'appointment_date', 'appointment_time'], is_active=True ) # Create messages and message logs for upcoming appointments upcoming_appointments = [a for a in appointments if a.scheduled_date >= date.today()] for appt in random.sample(upcoming_appointments, min(20, len(upcoming_appointments))): message = Message.objects.create( tenant=tenant, channel=Message.Channel.SMS, recipient=str(appt.patient.phone), body=f"Reminder: Your appointment is on {appt.scheduled_date} at {appt.scheduled_time}", status=random.choice([Message.Status.SENT, Message.Status.DELIVERED]), sent_at=timezone.now() ) # Create message log MessageLog.objects.create( message=message, event_type=random.choice(list(MessageLog.EventType.choices))[0], details={'status': 'sent', 'provider': 'SMS Gateway'} ) # Create appointment reminders for upcoming appointments for appt in random.sample(upcoming_appointments, min(30, len(upcoming_appointments))): AppointmentReminder.objects.create( appointment=appt, reminder_type=random.choice(list(AppointmentReminder.ReminderType.choices))[0], scheduled_for=timezone.now() - timedelta(hours=random.randint(1, 48)), sent_at=timezone.now() if random.choice([True, False]) else None, status=random.choice(list(AppointmentReminder.Status.choices))[0] ) # Create appointment confirmations for confirmed appointments confirmed_appointments = [a for a in appointments if a.status == Appointment.Status.CONFIRMED] for appt in random.sample(confirmed_appointments, min(25, len(confirmed_appointments))): AppointmentConfirmation.objects.create( appointment=appt, token=secrets.token_urlsafe(32), status=AppointmentConfirmation.Status.CONFIRMED, confirmation_method=random.choice(list(AppointmentConfirmation.ConfirmationMethod.choices))[0], confirmed_at=timezone.now() - timedelta(hours=random.randint(1, 72)), expires_at=timezone.now() + timedelta(days=7), sent_at=timezone.now() - timedelta(hours=random.randint(24, 96)) ) self.stdout.write(f' Created communication records') def generate_integration_data(self, tenant, patients, appointments, clinics, users): """Generate referrals, integration records, and core models.""" completed_appointments = [a for a in appointments if a.status == Appointment.Status.COMPLETED] # Create referrals referrals = [] for _ in range(min(10, len(completed_appointments))): appt = random.choice(completed_appointments) from_clinic = appt.clinic to_clinics = Clinic.objects.filter(tenant=tenant).exclude(id=from_clinic.id) if to_clinics.exists(): to_clinic = random.choice(to_clinics) referral = Referral.objects.create( tenant=tenant, patient=appt.patient, from_clinic=from_clinic, to_clinic=to_clinic, referral_type=random.choice(list(Referral.ReferralType.choices))[0], priority=random.choice(list(Referral.Priority.choices))[0], reason='Patient requires additional specialized care', referred_by=appt.provider.user, status=random.choice(list(Referral.Status.choices))[0] ) referrals.append(referral) # Create consents for patients with expiry dates for patient in random.sample(patients, min(25, len(patients))): for _ in range(random.randint(1, 2)): signed_date = timezone.now() - timedelta(days=random.randint(0, 180)) # Set expiry date 1 year from signing, with some variation expiry_date = (signed_date + timedelta(days=365)).date() Consent.objects.create( tenant=tenant, patient=patient, consent_type=random.choice(list(Consent.ConsentType.choices))[0], content_text='Standard consent form content', signed_by_name=patient.caregiver_name or patient.full_name_en, signed_by_relationship=patient.caregiver_relationship or 'Self', signed_at=signed_date, signature_method=random.choice(list(Consent.SignatureMethod.choices))[0], expiry_date=expiry_date, version=1, is_active=True ) # Create audit logs for various actions using GenericForeignKey from django.contrib.contenttypes.models import ContentType patient_ct = ContentType.objects.get_for_model(Patient) for _ in range(50): AuditLog.objects.create( tenant=tenant, content_type=patient_ct, object_id=random.choice(patients).id, action=random.choice(list(AuditLog.Action.choices))[0], user=random.choice(users), changes={'action': 'performed on patient record'}, ip_address=f'192.168.1.{random.randint(1, 255)}' ) # Create some external orders (lab/radiology) for _ in range(min(10, len(completed_appointments))): appt = random.choice(completed_appointments) ExternalOrder.objects.create( tenant=tenant, patient=appt.patient, order_type=random.choice(list(ExternalOrder.OrderType.choices))[0], order_details={ 'tests': ['CBC', 'Metabolic Panel'] if random.choice([True, False]) else ['X-Ray Chest'], 'notes': 'Routine screening' }, status=random.choice(list(ExternalOrder.Status.choices))[0], ordered_by=appt.provider.user, ordered_at=timezone.now() - timedelta(days=random.randint(1, 30)) ) # Create PayerContract for insurance companies for insurance_company in SAUDI_INSURANCE_COMPANIES[:5]: PayerContract.objects.create( tenant=tenant, payer_code=f"INS{random.randint(1000, 9999)}", payer_name=insurance_company, credentials={'api_key': 'SAMPLE_KEY', 'api_secret': 'SAMPLE_SECRET'}, endpoints={ 'eligibility': 'https://api.example.com/eligibility', 'claims': 'https://api.example.com/claims' }, supports_eligibility=True, supports_prior_auth=True, supports_claims=True, is_active=True ) self.stdout.write(f' Created integration and core records including PayerContract') def generate_hr_data(self, tenant, users): """Generate HR data including attendance, schedules, holidays, leave requests, and leave balances.""" # Generate HR schedules for employees (Sunday-Thursday work week) hr_schedules = [] for user in users: # Saudi work week: Sunday to Thursday for day in ['SUN', 'MON', 'TUE', 'WED', 'THU']: hr_schedule = HRSchedule.objects.create( tenant=tenant, employee=user, day_of_week=day, start_time='08:00', end_time='17:00', is_active=True ) hr_schedules.append(hr_schedule) self.created_counts.setdefault('hr_schedules', 0) self.created_counts['hr_schedules'] += len(hr_schedules) # Generate attendance records for the past 30 days attendances = [] for user in users: for days_ago in range(30): attendance_date = date.today() - timedelta(days=days_ago) # Skip Fridays and Saturdays (Saudi weekend) if attendance_date.weekday() in [4, 5]: continue # Random attendance status status = random.choices( [Attendance.Status.PRESENT, Attendance.Status.LATE, Attendance.Status.ABSENT, Attendance.Status.HALF_DAY, Attendance.Status.LEAVE], weights=[70, 10, 5, 10, 5] )[0] if status not in [Attendance.Status.ABSENT, Attendance.Status.LEAVE]: from datetime import time # Determine check-in time based on status if status == Attendance.Status.LATE: check_in_hour = random.choice([8, 9]) check_in_minute = random.choice([15, 30, 45]) else: check_in_hour = 8 check_in_minute = random.choice([0, 5, 10]) # Determine check-out time based on status if status == Attendance.Status.HALF_DAY: check_out_hour = random.choice([12, 13]) check_out_minute = random.choice([0, 30]) else: check_out_hour = random.choice([16, 17, 18]) check_out_minute = random.choice([0, 15, 30, 45]) attendance = Attendance.objects.create( tenant=tenant, employee=user, date=attendance_date, check_in=time(check_in_hour, check_in_minute), check_out=time(check_out_hour, check_out_minute), status=status, notes='' if status == Attendance.Status.PRESENT else f'{status} - recorded' ) # hours_worked is automatically calculated in the model's save method else: # For ABSENT or LEAVE status, no check-in/check-out times attendance = Attendance.objects.create( tenant=tenant, employee=user, date=attendance_date, status=status, notes='Absent' if status == Attendance.Status.ABSENT else 'On approved leave' ) attendances.append(attendance) self.created_counts.setdefault('attendances', 0) self.created_counts['attendances'] += len(attendances) # Generate holidays holidays_data = [ ('Saudi National Day', date(date.today().year, 9, 23), True), ('Founding Day', date(date.today().year, 2, 22), True), ('Eid Al-Fitr', date.today() + timedelta(days=random.randint(30, 90)), False), ('Eid Al-Adha', date.today() + timedelta(days=random.randint(120, 180)), False), ] holidays = [] for name, holiday_date, is_recurring in holidays_data: holiday = Holiday.objects.create( tenant=tenant, name=name, date=holiday_date, is_recurring=is_recurring, description=f'{name} - Public holiday' ) holidays.append(holiday) self.created_counts.setdefault('holidays', 0) self.created_counts['holidays'] += len(holidays) # Generate leave requests leave_requests = [] current_year = date.today().year for user in random.sample(users, min(10, len(users))): for _ in range(random.randint(1, 3)): leave_type = random.choice(list(LeaveRequest.LeaveType.choices))[0] start_date = date.today() + timedelta(days=random.randint(-60, 60)) days_requested = random.randint(1, 7) end_date = start_date + timedelta(days=days_requested - 1) # Determine status based on date if start_date < date.today(): status = random.choice([ LeaveRequest.Status.APPROVED, LeaveRequest.Status.REJECTED ]) else: status = random.choice([ LeaveRequest.Status.PENDING, LeaveRequest.Status.APPROVED ]) leave_request = LeaveRequest.objects.create( tenant=tenant, employee=user, leave_type=leave_type, start_date=start_date, end_date=end_date, days_requested=days_requested, reason=f'Request for {leave_type.lower()} leave', status=status ) # Add reviewer info if approved or rejected if status in [LeaveRequest.Status.APPROVED, LeaveRequest.Status.REJECTED]: admin_users = [u for u in users if u.role == User.Role.ADMIN] if admin_users: leave_request.reviewed_by = random.choice(admin_users) leave_request.reviewed_at = timezone.now() - timedelta(days=random.randint(1, 5)) leave_request.reviewer_comments = 'Approved' if status == LeaveRequest.Status.APPROVED else 'Not approved at this time' leave_request.save() leave_requests.append(leave_request) self.created_counts.setdefault('leave_requests', 0) self.created_counts['leave_requests'] += len(leave_requests) # Generate leave balances for all users leave_balances = [] for user in users: # Annual leave balance annual_balance = LeaveBalance.objects.create( tenant=tenant, employee=user, year=current_year, leave_type=LeaveRequest.LeaveType.ANNUAL, total_days=Decimal('21.0'), # Standard Saudi annual leave used_days=Decimal(str(random.uniform(0, 10))) ) leave_balances.append(annual_balance) # Sick leave balance sick_balance = LeaveBalance.objects.create( tenant=tenant, employee=user, year=current_year, leave_type=LeaveRequest.LeaveType.SICK, total_days=Decimal('15.0'), used_days=Decimal(str(random.uniform(0, 5))) ) leave_balances.append(sick_balance) self.created_counts.setdefault('leave_balances', 0) self.created_counts['leave_balances'] += len(leave_balances) self.stdout.write(f' Created HR records (schedules, attendance, holidays, leave requests, leave balances)') def generate_documents_data(self, tenant, patients, appointments, users): """Generate document templates and clinical notes with audit trail.""" # Generate document templates templates_data = [ { 'name': 'Medical Progress Note Template', 'category': 'medical', 'description': 'Standard template for medical progress notes', 'content': '''MEDICAL PROGRESS NOTE Patient: {{patient_name}} MRN: {{patient_mrn}} Date: {{date}} Provider: {{provider_name}} Chief Complaint: {{chief_complaint}} Assessment: {{assessment}} Plan: {{plan}} Provider Signature: _______________ Date: {{date}}''' }, { 'name': 'Therapy Session Note Template', 'category': 'progress', 'description': 'Template for therapy session documentation', 'content': '''THERAPY SESSION NOTE Patient: {{patient_name}} Session Date: {{date}} Therapist: {{provider_name}} Session Type: {{session_type}} Goals Addressed: {{goals}} Activities: {{activities}} Patient Response: {{response}} Recommendations: {{recommendations}} Therapist Signature: _______________''' }, { 'name': 'Assessment Report Template', 'category': 'assessment', 'description': 'Comprehensive assessment report template', 'content': '''ASSESSMENT REPORT Patient Information: Name: {{patient_name}} DOB: {{patient_dob}} Age: {{patient_age}} Assessment Date: {{date}} Reason for Referral: {{referral_reason}} Assessment Findings: {{findings}} Recommendations: {{recommendations}} Assessor: {{provider_name}} Signature: _______________ Date: {{date}}''' }, ] templates = [] for template_data in templates_data: template = DocumentTemplate.objects.create( name=template_data['name'], category=template_data['category'], description=template_data['description'], content=template_data['content'], is_active=True, created_by=random.choice(users) ) templates.append(template) self.created_counts.setdefault('document_templates', 0) self.created_counts['document_templates'] += len(templates) # Generate clinical notes for completed appointments completed_appointments = [a for a in appointments if a.status == Appointment.Status.COMPLETED] notes = [] for appt in random.sample(completed_appointments, min(30, len(completed_appointments))): # Randomly choose to use a template or not template = random.choice(templates) if random.choice([True, False]) else None # Determine status based on how old the appointment is days_since = (date.today() - appt.scheduled_date).days if days_since > 7: status = random.choice(['draft', 'final', 'final', 'final']) # More likely to be final else: status = random.choice(['draft', 'draft', 'final']) # More likely to be draft note = ClinicalNote.objects.create( patient=appt.patient, template=template, title=f"{appt.service_type} - {appt.scheduled_date}", content=f'''Patient: {appt.patient.full_name_en} Date: {appt.scheduled_date} Provider: {appt.provider.user.get_full_name()} Session Summary: Patient attended {appt.service_type.lower()} session. {random.choice([ "Good progress noted during session.", "Patient engaged well with activities.", "Continued work on established goals.", "Patient showed improvement in targeted areas." ])} Observations: {random.choice([ "Patient was cooperative and attentive.", "Patient demonstrated good effort throughout session.", "Patient required minimal prompting.", "Patient showed enthusiasm for activities." ])} Plan: Continue current treatment plan. Next session scheduled.''', status=status, author=appt.provider.user ) # If finalized, add finalization details if status == 'final': note.finalized_at = timezone.now() - timedelta(days=random.randint(0, days_since)) note.finalized_by = appt.provider.user note.save() notes.append(note) # Create audit log for note creation NoteAuditLog.objects.create( note=note, action='created', user=appt.provider.user, timestamp=timezone.now() - timedelta(days=days_since), ip_address=f'192.168.1.{random.randint(1, 255)}' ) # If finalized, add finalization audit log if status == 'final': NoteAuditLog.objects.create( note=note, action='finalized', user=note.finalized_by, timestamp=note.finalized_at, ip_address=f'192.168.1.{random.randint(1, 255)}' ) self.created_counts.setdefault('clinical_notes', 0) self.created_counts['clinical_notes'] += len(notes) # Generate addendums for some finalized notes finalized_notes = [n for n in notes if n.status == 'final'] addendums = [] for note in random.sample(finalized_notes, min(5, len(finalized_notes))): addendum = NoteAddendum.objects.create( note=note, content=random.choice([ 'Additional information: Patient contacted after session to report continued progress at home.', 'Correction: Session duration was 45 minutes, not 30 minutes as initially documented.', 'Update: Follow-up appointment scheduled for next week.', 'Addendum: Parent reported improvement in targeted behaviors since last session.' ]), reason=random.choice([ 'Additional information', 'Correction', 'Update', 'Clarification' ]), author=note.author ) addendums.append(addendum) # Update note status to amended note.status = 'amended' note.save() # Create audit log for addendum NoteAuditLog.objects.create( note=note, action='amended', user=addendum.author, timestamp=timezone.now(), ip_address=f'192.168.1.{random.randint(1, 255)}', changes={'addendum_id': addendum.id, 'reason': addendum.reason} ) self.created_counts.setdefault('note_addendums', 0) self.created_counts['note_addendums'] += len(addendums) # Generate view audit logs for random notes for note in random.sample(notes, min(20, len(notes))): for _ in range(random.randint(1, 3)): NoteAuditLog.objects.create( note=note, action='viewed', user=random.choice(users), timestamp=timezone.now() - timedelta(days=random.randint(0, 30)), ip_address=f'192.168.1.{random.randint(1, 255)}' ) self.stdout.write(f' Created documents data (templates, notes, addendums, audit logs)') def generate_mdt_data(self, tenant, patients, users, clinics): """Generate MDT notes with contributions, approvals, and mentions.""" from mdt.models import MDTNote, MDTContribution, MDTApproval, MDTMention, MDTAttachment # Get clinical users who can participate in MDT clinical_users = [u for u in users if u.role in [ User.Role.DOCTOR, User.Role.NURSE, User.Role.OT, User.Role.SLP, User.Role.ABA ]] if not clinical_users: return mdt_notes = [] # Generate MDT notes for complex cases for _ in range(min(10, len(patients))): patient = random.choice(patients) initiator = random.choice(clinical_users) # Determine status based on random progression status_weights = [30, 25, 35, 10] # DRAFT, PENDING_APPROVAL, FINALIZED, ARCHIVED status = random.choices( list(MDTNote.Status.choices), weights=status_weights )[0][0] mdt_note = MDTNote.objects.create( tenant=tenant, patient=patient, title=random.choice([ 'Complex Case Discussion', 'Treatment Plan Review', 'Multidisciplinary Assessment', 'Care Coordination Meeting', 'Progress Review Discussion' ]), purpose=random.choice([ 'Discuss comprehensive treatment approach for patient with multiple needs', 'Review progress across all therapy disciplines', 'Coordinate care plan between departments', 'Address behavioral and developmental concerns', 'Plan transition to next phase of treatment' ]), status=status, initiated_by=initiator, version=1 ) # Add summary and recommendations if finalized if status in [MDTNote.Status.FINALIZED, MDTNote.Status.ARCHIVED]: mdt_note.summary = 'Team discussed patient progress and coordinated treatment approach across disciplines.' mdt_note.recommendations = 'Continue current therapy plan with increased coordination between departments.' mdt_note.finalized_at = timezone.now() - timedelta(days=random.randint(1, 30)) mdt_note.save() mdt_notes.append(mdt_note) # Add contributions from different departments num_contributors = random.randint(2, 4) contributors = random.sample(clinical_users, min(num_contributors, len(clinical_users))) for contributor in contributors: # Find appropriate clinic for contributor clinic_map = { User.Role.DOCTOR: Clinic.Specialty.MEDICAL, User.Role.NURSE: Clinic.Specialty.NURSING, User.Role.OT: Clinic.Specialty.OT, User.Role.SLP: Clinic.Specialty.SLP, User.Role.ABA: Clinic.Specialty.ABA, } specialty = clinic_map.get(contributor.role) contributor_clinic = next((c for c in clinics if c.specialty == specialty), random.choice(clinics)) contribution = MDTContribution.objects.create( mdt_note=mdt_note, contributor=contributor, clinic=contributor_clinic, content=random.choice([ f'From {contributor_clinic.name_en} perspective: Patient shows good progress in therapy sessions.', f'{contributor_clinic.name_en} assessment: Patient demonstrates improvement in targeted areas.', f'Clinical observations from {contributor_clinic.name_en}: Patient is responding well to interventions.', f'{contributor_clinic.name_en} recommendation: Continue current treatment approach with modifications.' ]), is_final=status != MDTNote.Status.DRAFT ) # Add mentions to some contributions if random.choice([True, False]) and len(contributors) > 1: mentioned = random.choice([u for u in contributors if u != contributor]) contribution.mentioned_users.add(mentioned) MDTMention.objects.create( contribution=contribution, mentioned_user=mentioned, notified_at=timezone.now() - timedelta(hours=random.randint(1, 48)), viewed_at=timezone.now() - timedelta(hours=random.randint(0, 24)) if random.choice([True, False]) else None ) # Add approvals if status is PENDING_APPROVAL or FINALIZED if status in [MDTNote.Status.PENDING_APPROVAL, MDTNote.Status.FINALIZED, MDTNote.Status.ARCHIVED]: # Get senior therapists from different departments senior_users = [u for u in clinical_users if u.role in [ User.Role.DOCTOR, User.Role.OT, User.Role.SLP, User.Role.ABA ]] if len(senior_users) >= 2: # Select 2 approvers from different departments approver1 = random.choice(senior_users) approver2 = random.choice([u for u in senior_users if u.role != approver1.role]) for approver in [approver1, approver2]: specialty = clinic_map.get(approver.role) approver_clinic = next((c for c in clinics if c.specialty == specialty), random.choice(clinics)) approval = MDTApproval.objects.create( mdt_note=mdt_note, approver=approver, clinic=approver_clinic, approved=status in [MDTNote.Status.FINALIZED, MDTNote.Status.ARCHIVED], approved_at=timezone.now() - timedelta(days=random.randint(1, 15)) if status in [MDTNote.Status.FINALIZED, MDTNote.Status.ARCHIVED] else None, comments=random.choice([ 'Approved. Excellent collaborative plan.', 'Approved. Good coordination between departments.', 'Approved with minor suggestions for follow-up.', '' ]) if status in [MDTNote.Status.FINALIZED, MDTNote.Status.ARCHIVED] else '' ) self.created_counts.setdefault('mdt_notes', 0) self.created_counts['mdt_notes'] += len(mdt_notes) self.stdout.write(f' Created {len(mdt_notes)} MDT notes with contributions and approvals') def print_summary(self): """Print summary of created data.""" self.stdout.write('\n' + '='*60) self.stdout.write(self.style.SUCCESS('DATA GENERATION SUMMARY')) self.stdout.write('='*60) for model_name, count in sorted(self.created_counts.items()): self.stdout.write(f' {model_name.title()}: {count}') self.stdout.write('='*60)