358 lines
11 KiB
Python
358 lines
11 KiB
Python
"""
|
|
Shared data generation functions for Saudi healthcare data.
|
|
Contains common generators used across multiple data generation modules.
|
|
"""
|
|
import os
|
|
import django
|
|
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
|
|
try:
|
|
django.setup()
|
|
except Exception as e:
|
|
print(f"Django setup failed: {e}")
|
|
print("Please run this script using: python manage.py shell -c \"exec(open('hr_data.py').read())\"")
|
|
exit(1)
|
|
|
|
import random
|
|
import uuid
|
|
from datetime import datetime, timedelta, date
|
|
from decimal import Decimal
|
|
from django.utils import timezone as django_timezone
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from .constants import (
|
|
SAUDI_FIRST_NAMES_MALE, SAUDI_FIRST_NAMES_FEMALE, SAUDI_FAMILY_NAMES,
|
|
SAUDI_CITIES, SAUDI_PROVINCES, SAUDI_LICENSE_PREFIXES, SAUDI_JOB_TITLES
|
|
)
|
|
|
|
from accounts.models import User
|
|
|
|
|
|
# ================================
|
|
# PHONE AND ID GENERATORS
|
|
# ================================
|
|
|
|
def generate_saudi_mobile_e164():
|
|
"""Generate Saudi E.164 mobile: +9665XXXXXXXX"""
|
|
return f"+9665{random.randint(10000000, 99999999)}"
|
|
|
|
|
|
def generate_saudi_phone():
|
|
"""Generate Saudi phone number"""
|
|
return f"+966{random.randint(11, 17)}{random.randint(1000000, 9999999)}"
|
|
|
|
|
|
def generate_saudi_license():
|
|
"""Generate Saudi medical license number (fictional format)"""
|
|
prefix = random.choice(SAUDI_LICENSE_PREFIXES)
|
|
return f"{prefix}-{random.randint(100000, 999999)}"
|
|
|
|
|
|
def generate_saudi_medical_record_number():
|
|
"""Generate medical record number"""
|
|
return f"MRN{random.randint(100000, 999999)}"
|
|
|
|
|
|
def generate_saudi_item_code():
|
|
"""Generate Saudi medical item code"""
|
|
return f"SAU-{random.randint(100000, 999999)}"
|
|
|
|
|
|
def generate_saudi_lot_number():
|
|
"""Generate Saudi lot number"""
|
|
return f"LOT{random.randint(2024, 2025)}{random.randint(100, 999)}"
|
|
|
|
|
|
def generate_saudi_po_number():
|
|
"""Generate Saudi purchase order number"""
|
|
return f"PO-{random.randint(2024, 2025)}-{random.randint(1000, 9999)}"
|
|
|
|
|
|
# ================================
|
|
# NAME AND USERNAME GENERATORS
|
|
# ================================
|
|
|
|
def generate_saudi_name(gender=None):
|
|
"""Generate a complete Saudi name"""
|
|
if gender is None:
|
|
gender = random.choice(['MALE', 'FEMALE'])
|
|
|
|
if gender == 'MALE':
|
|
first_name = random.choice(SAUDI_FIRST_NAMES_MALE)
|
|
else:
|
|
first_name = random.choice(SAUDI_FIRST_NAMES_FEMALE)
|
|
|
|
last_name = random.choice(SAUDI_FAMILY_NAMES)
|
|
|
|
return {
|
|
'first_name': first_name,
|
|
'last_name': last_name,
|
|
'full_name': f"{first_name} {last_name}"
|
|
}
|
|
|
|
|
|
def tenant_scoped_unique_username(tenant, base_username: str) -> str:
|
|
"""
|
|
Make username unique within a tenant (AUTH is tenant-scoped).
|
|
"""
|
|
uname = base_username
|
|
i = 1
|
|
while User.objects.filter(tenant=tenant, username=uname).exists():
|
|
i += 1
|
|
uname = f"{base_username}{i}"
|
|
return uname
|
|
|
|
|
|
def generate_saudi_email(first_name, last_name, domain="email.com"):
|
|
"""Generate Saudi-style email address"""
|
|
return f"{first_name.lower()}.{last_name.lower().replace('-', '').replace(' ', '')}@{domain}"
|
|
|
|
|
|
# ================================
|
|
# ADDRESS GENERATORS
|
|
# ================================
|
|
|
|
def generate_saudi_address():
|
|
"""Generate a complete Saudi address"""
|
|
return {
|
|
'address_line_1': f"{random.randint(1, 999)} {random.choice(['King Fahd Road', 'Prince Sultan Street', 'Al Malik Road'])}",
|
|
'address_line_2': f"Apt {random.randint(1, 50)}" if random.choice([True, False]) else None,
|
|
'city': random.choice(SAUDI_CITIES),
|
|
'state': random.choice(SAUDI_PROVINCES),
|
|
'postal_code': f"{random.randint(10000, 99999)}",
|
|
'country': 'Saudi Arabia'
|
|
}
|
|
|
|
|
|
# ================================
|
|
# MEDICAL DATA GENERATORS
|
|
# ================================
|
|
|
|
def pick_job_title_for_department(department) -> str:
|
|
"""Pick appropriate job title based on department type"""
|
|
dtype = getattr(department, 'department_type', 'ADMINISTRATIVE') or 'ADMINISTRATIVE'
|
|
|
|
if dtype == 'CLINICAL':
|
|
return random.choice([
|
|
'Consultant Physician', 'Senior Physician', 'Physician', 'Resident Physician', 'Intern',
|
|
'Chief Nurse', 'Nurse Manager', 'Senior Nurse', 'Staff Nurse',
|
|
'Nurse Practitioner', 'Clinical Nurse Specialist', 'Charge Nurse',
|
|
'Pharmacist', 'Clinical Pharmacist', 'Pharmacy Technician',
|
|
'Radiologist', 'Radiology Technician', 'Medical Technologist',
|
|
'Laboratory Technician', 'Physical Therapist', 'Respiratory Therapist'
|
|
])
|
|
elif dtype == 'SUPPORT':
|
|
return random.choice([
|
|
'Security Officer', 'Security Guard', 'Maintenance Technician',
|
|
'Housekeeping Supervisor', 'Housekeeper', 'Food Service Manager',
|
|
'Cook', 'Kitchen Assistant', 'Transport Aide', 'Receptionist'
|
|
])
|
|
else:
|
|
return random.choice([
|
|
'Chief Executive Officer', 'Chief Operating Officer', 'Administrator',
|
|
'Assistant Administrator', 'Department Manager', 'Supervisor',
|
|
'Administrative Assistant', 'Secretary', 'Clerk', 'Coordinator'
|
|
])
|
|
|
|
|
|
def infer_role_from_title(job_title: str) -> str:
|
|
"""Infer role from job title"""
|
|
jt = (job_title or '').lower()
|
|
if 'physician' in jt:
|
|
return 'PHYSICIAN'
|
|
if 'nurse practitioner' in jt:
|
|
return 'NURSE_PRACTITIONER'
|
|
if 'nurse' in jt:
|
|
return 'NURSE'
|
|
if 'pharmac' in jt:
|
|
return 'PHARMACIST'
|
|
if 'radiolog' in jt and 'techn' not in jt:
|
|
return 'RADIOLOGIST'
|
|
if 'radiolog' in jt and 'techn' in jt:
|
|
return 'RAD_TECH'
|
|
if 'laborator' in jt:
|
|
return 'LAB_TECH'
|
|
if any(k in jt for k in ['chief', 'director', 'manager', 'admin']):
|
|
return 'ADMIN'
|
|
return 'CLERICAL'
|
|
|
|
|
|
def generate_saudi_provider_email(first_name, last_name, tenant_domain):
|
|
"""Generate provider email for tenant"""
|
|
return f"{first_name.lower()}.{last_name.lower().replace('-', '')}@{tenant_domain}"
|
|
|
|
|
|
# ================================
|
|
# FINANCIAL GENERATORS
|
|
# ================================
|
|
|
|
def generate_saudi_tax_id():
|
|
"""Generate Saudi tax ID"""
|
|
return f"TAX-{random.randint(100000000, 999999999)}"
|
|
|
|
|
|
def generate_saudi_vat_number():
|
|
"""Generate Saudi VAT number (15 digits)"""
|
|
return f"{random.randint(10**14, 10**15 - 1)}"
|
|
|
|
|
|
def generate_saudi_crn():
|
|
"""Generate Saudi Commercial Registration Number"""
|
|
return f"{random.randint(10**9, 10**10 - 1)}"
|
|
|
|
|
|
# ================================
|
|
# TIME AND DATE GENERATORS
|
|
# ================================
|
|
|
|
def generate_birth_date(min_age=1, max_age=85):
|
|
"""Generate birth date within age range"""
|
|
birth_year = random.randint(1939, 2006)
|
|
birth_month = random.randint(1, 12)
|
|
birth_day = random.randint(1, 28)
|
|
return date(birth_year, birth_month, birth_day)
|
|
|
|
|
|
def generate_hire_date(max_years_ago=20):
|
|
"""Generate hire date within reasonable range"""
|
|
start_date = django_timezone.now().date() - timedelta(days=365 * max_years_ago)
|
|
end_date = django_timezone.now().date() - timedelta(days=30)
|
|
days_range = (end_date - start_date).days
|
|
random_days = random.randint(0, days_range)
|
|
return start_date + timedelta(days=random_days)
|
|
|
|
|
|
def generate_future_date(days_ahead=365):
|
|
"""Generate future date"""
|
|
return django_timezone.now().date() + timedelta(days=random.randint(1, days_ahead))
|
|
|
|
|
|
def generate_past_date(days_back=365):
|
|
"""Generate past date"""
|
|
return django_timezone.now().date() - timedelta(days=random.randint(1, days_back))
|
|
|
|
|
|
# ================================
|
|
# CLINICAL DATA GENERATORS
|
|
# ================================
|
|
|
|
def generate_vital_signs(patient_age=None):
|
|
"""Generate realistic vital signs based on age"""
|
|
if patient_age is None:
|
|
patient_age = random.randint(1, 85)
|
|
|
|
# Temperature (Celsius)
|
|
temperature = round(random.uniform(36.0, 39.5), 1)
|
|
|
|
# Blood pressure
|
|
if patient_age < 18:
|
|
systolic_bp = random.randint(90, 120)
|
|
diastolic_bp = random.randint(50, 80)
|
|
elif patient_age < 65:
|
|
systolic_bp = random.randint(100, 140)
|
|
diastolic_bp = random.randint(60, 90)
|
|
else:
|
|
systolic_bp = random.randint(110, 160)
|
|
diastolic_bp = random.randint(70, 100)
|
|
|
|
# Heart rate
|
|
if patient_age < 1:
|
|
heart_rate = random.randint(100, 160)
|
|
elif patient_age < 12:
|
|
heart_rate = random.randint(80, 120)
|
|
else:
|
|
heart_rate = random.randint(60, 100)
|
|
|
|
# Respiratory rate
|
|
if patient_age < 1:
|
|
respiratory_rate = random.randint(30, 60)
|
|
elif patient_age < 12:
|
|
respiratory_rate = random.randint(18, 30)
|
|
else:
|
|
respiratory_rate = random.randint(12, 20)
|
|
|
|
# Oxygen saturation
|
|
oxygen_saturation = random.randint(95, 100)
|
|
|
|
# Weight and height
|
|
if patient_age < 18:
|
|
weight = round(random.uniform(3.0, 70.0), 1)
|
|
height = round(random.uniform(50.0, 180.0), 1)
|
|
else:
|
|
weight = round(random.uniform(45.0, 150.0), 1)
|
|
height = round(random.uniform(150.0, 200.0), 1)
|
|
|
|
return {
|
|
'temperature': temperature,
|
|
'systolic_bp': systolic_bp,
|
|
'diastolic_bp': diastolic_bp,
|
|
'heart_rate': heart_rate,
|
|
'respiratory_rate': respiratory_rate,
|
|
'oxygen_saturation': oxygen_saturation,
|
|
'weight': weight,
|
|
'height': height
|
|
}
|
|
|
|
|
|
def generate_lab_values(test_type='QUANTITATIVE', reference_range=None):
|
|
"""Generate realistic lab values"""
|
|
if test_type == 'QUANTITATIVE' and reference_range:
|
|
# Generate value within or outside reference range
|
|
if random.random() < 0.8: # 80% normal
|
|
low = float(reference_range.get('range_low', 0))
|
|
high = float(reference_range.get('range_high', 100))
|
|
value = round(random.uniform(low, high), 2)
|
|
abnormal_flag = 'N'
|
|
else: # 20% abnormal
|
|
low = float(reference_range.get('range_low', 0))
|
|
high = float(reference_range.get('range_high', 100))
|
|
if random.choice([True, False]):
|
|
value = round(random.uniform(high * 1.1, high * 2.0), 2)
|
|
abnormal_flag = 'H'
|
|
else:
|
|
value = round(random.uniform(low * 0.1, low * 0.9), 2)
|
|
abnormal_flag = 'L'
|
|
else:
|
|
# Qualitative result
|
|
qualitative_results = ['Negative', 'Positive', 'Normal', 'Abnormal', 'Detected', 'Not Detected']
|
|
value = random.choice(qualitative_results)
|
|
abnormal_flag = 'A' if value in ['Positive', 'Abnormal', 'Detected'] else 'N'
|
|
|
|
return value, abnormal_flag
|
|
|
|
|
|
# ================================
|
|
# UTILITY FUNCTIONS
|
|
# ================================
|
|
|
|
def safe_choice(seq):
|
|
"""Safe choice that handles empty sequences"""
|
|
return random.choice(seq) if seq else None
|
|
|
|
|
|
def random_percentage():
|
|
"""Generate random percentage 0-100"""
|
|
return random.randint(0, 100)
|
|
|
|
|
|
def random_decimal(min_val, max_val, precision="0.01"):
|
|
"""Generate random decimal within range"""
|
|
q = Decimal(precision)
|
|
return Decimal(str(random.uniform(float(min_val), float(max_val)))).quantize(q)
|
|
|
|
|
|
def generate_uuid():
|
|
"""Generate UUID string"""
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
def generate_unique_code(prefix, existing_codes=None):
|
|
"""Generate unique code with prefix"""
|
|
if existing_codes is None:
|
|
existing_codes = set()
|
|
|
|
while True:
|
|
code = f"{prefix}{random.randint(100000, 999999)}"
|
|
if code not in existing_codes:
|
|
return code
|