Marwan Alwali ab2c4a36c5 update
2025-10-02 10:13:03 +03:00

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