hospital-management/emr_data.py
Marwan Alwali 84c1fb798e update
2025-09-08 19:52:52 +03:00

1285 lines
47 KiB
Python

import os
import django
# Set up Django environment
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
django.setup()
import random
from datetime import datetime, date, time, timedelta
from django.utils import timezone as django_timezone
from django.contrib.auth import get_user_model
from emr.models import Encounter, VitalSigns, ProblemList, CarePlan, ClinicalNote, NoteTemplate
from patients.models import PatientProfile
from appointments.models import AppointmentRequest
from inpatients.models import Admission
from core.models import Tenant
import uuid
from decimal import Decimal
User = get_user_model()
# Saudi-specific clinical data
SAUDI_ENCOUNTER_TYPES = [
'CONSULTATION', 'FOLLOW_UP', 'EMERGENCY', 'PROCEDURE', 'SURGERY',
'DIAGNOSTIC', 'THERAPY', 'SCREENING', 'VACCINATION', 'TELEMEDICINE'
]
SAUDI_ENCOUNTER_CLASSES = [
'OUTPATIENT', 'INPATIENT', 'EMERGENCY', 'OBSERVATION',
'DAY_SURGERY', 'TELEMEDICINE', 'HOME_VISIT'
]
SAUDI_CHIEF_COMPLAINTS = [
'Chest pain and shortness of breath',
'Abdominal pain and nausea',
'Headache and dizziness',
'Back pain and stiffness',
'Fever and cough',
'Joint pain and swelling',
'Fatigue and weakness',
'Skin rash and itching',
'Diabetes follow-up',
'Hypertension monitoring',
'Regular health checkup',
'Vaccination appointment',
'Pre-operative consultation',
'Post-operative follow-up',
'Pregnancy consultation',
'Child wellness visit',
'Mental health consultation',
'Physical therapy session',
'Diagnostic imaging',
'Laboratory test follow-up',
'Cardiac symptoms evaluation',
'Respiratory symptoms',
'Gastrointestinal complaints',
'Neurological symptoms',
'Endocrine disorders follow-up'
]
SAUDI_LOCATIONS = [
'Main Building - Floor 1',
'Main Building - Floor 2',
'Main Building - Floor 3',
'Emergency Wing',
'Outpatient Clinic - Wing A',
'Outpatient Clinic - Wing B',
'Surgical Suite - Floor 4',
'Radiology Department',
'Laboratory Building',
'Pediatric Wing',
'ICU - Floor 5',
'Cardiology Unit',
'Maternity Ward',
'Dialysis Center'
]
SAUDI_PROBLEM_TYPES = [
'DIAGNOSIS', 'SYMPTOM', 'RISK_FACTOR', 'ALLERGY',
'MEDICATION_INTOLERANCE', 'FAMILY_HISTORY', 'SOCIAL_HISTORY'
]
SAUDI_COMMON_PROBLEMS = [
('Diabetes Mellitus Type 2', 'E11.9', 'ICD10'),
('Hypertension', 'I10', 'ICD10'),
('Hyperlipidemia', 'E78.5', 'ICD10'),
('Asthma', 'J45.9', 'ICD10'),
('Chronic Kidney Disease', 'N18.9', 'ICD10'),
('Osteoarthritis', 'M19.9', 'ICD10'),
('Depression', 'F32.9', 'ICD10'),
('Anxiety Disorder', 'F41.9', 'ICD10'),
('Obesity', 'E66.9', 'ICD10'),
('Gastroesophageal Reflux', 'K21.9', 'ICD10'),
('Migraine', 'G43.9', 'ICD10'),
('Allergic Rhinitis', 'J30.9', 'ICD10'),
('Hypothyroidism', 'E03.9', 'ICD10'),
('Coronary Artery Disease', 'I25.9', 'ICD10'),
('Chronic Obstructive Pulmonary Disease', 'J44.9', 'ICD10')
]
SAUDI_CARE_PLAN_TYPES = [
'TREATMENT', 'PREVENTION', 'REHABILITATION', 'CHRONIC_CARE',
'ACUTE_CARE', 'SURGICAL', 'MEDICATION_MANAGEMENT', 'LIFESTYLE'
]
SAUDI_NOTE_TYPES = [
'CONSULTATION', 'PROGRESS', 'DISCHARGE', 'PROCEDURE',
'SURGERY', 'EMERGENCY', 'NURSING', 'THERAPY',
'DIAGNOSTIC', 'MEDICATION_REVIEW', 'FOLLOW_UP'
]
def get_providers_for_emr(tenants):
"""Get healthcare providers for EMR data creation"""
providers = []
for tenant in tenants:
tenant_providers = User.objects.filter(
tenant=tenant,
is_active=True,
role__in=['PHYSICIAN', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'SURGEON', 'RADIOLOGIST']
)
providers.extend(list(tenant_providers))
# Create mock providers if none exist
if not providers:
providers = create_mock_providers_for_emr(tenants)
return providers
def create_mock_providers_for_emr(tenants):
"""Create mock healthcare providers for EMR data"""
providers = []
mock_providers_data = [
{'first_name': 'Ahmad', 'last_name': 'Al-Rashid', 'role': 'PHYSICIAN'},
{'first_name': 'Fatima', 'last_name': 'Al-Ghamdi', 'role': 'PHYSICIAN'},
{'first_name': 'Mohammed', 'last_name': 'Al-Otaibi', 'role': 'PHYSICIAN'},
{'first_name': 'Sarah', 'last_name': 'Al-Harbi', 'role': 'PHYSICIAN'},
{'first_name': 'Khalid', 'last_name': 'Al-Mutairi', 'role': 'SURGEON'},
{'first_name': 'Nora', 'last_name': 'Al-Zahrani', 'role': 'NURSE_PRACTITIONER'},
{'first_name': 'Omar', 'last_name': 'Al-Dawsari', 'role': 'PHYSICIAN'},
{'first_name': 'Layla', 'last_name': 'Al-Subai', 'role': 'PHYSICIAN'},
]
for tenant in tenants:
for provider_data in mock_providers_data:
try:
email = f"{provider_data['first_name'].lower()}.{provider_data['last_name'].lower().replace('-', '')}@{tenant.domain}"
existing_user = User.objects.filter(email=email).first()
if not existing_user:
user = User.objects.create_user(
email=email,
first_name=provider_data['first_name'],
last_name=provider_data['last_name'],
role=provider_data['role'],
tenant=tenant,
is_active=True,
password='temp_password_123'
)
providers.append(user)
else:
providers.append(existing_user)
except Exception as e:
print(f"Error creating mock provider {provider_data['first_name']} {provider_data['last_name']}: {e}")
continue
return providers
def create_note_templates(tenants):
"""Create note templates for different specialties"""
templates = []
template_configs = [
{
'name': 'General Consultation Note',
'note_type': 'CONSULTATION',
'specialty': 'FAMILY_MEDICINE',
'content': """
CHIEF COMPLAINT: [Chief Complaint]
HISTORY OF PRESENT ILLNESS:
[History details]
PAST MEDICAL HISTORY:
[Past medical history]
MEDICATIONS:
[Current medications]
ALLERGIES:
[Known allergies]
SOCIAL HISTORY:
[Social history]
PHYSICAL EXAMINATION:
General: [General appearance]
Vital Signs: [Vital signs]
HEENT: [Head, eyes, ears, nose, throat]
Cardiovascular: [Heart examination]
Respiratory: [Lung examination]
Abdomen: [Abdominal examination]
Extremities: [Extremity examination]
Neurological: [Neurological examination]
ASSESSMENT:
[Clinical impression]
PLAN:
[Treatment plan]
[Provider Name], [Title]
[Date and Time]
""",
'structured_fields': [
'chief_complaint', 'history_present_illness', 'past_medical_history',
'medications', 'allergies', 'social_history', 'physical_exam',
'assessment', 'plan'
]
},
{
'name': 'Cardiology Consultation',
'note_type': 'CONSULTATION',
'specialty': 'CARDIOLOGY',
'content': """
CHIEF COMPLAINT: [Cardiac symptoms]
HISTORY OF PRESENT ILLNESS:
[Cardiac history details]
CARDIOVASCULAR RISK FACTORS:
[Risk factors assessment]
MEDICATIONS:
[Cardiac medications]
PHYSICAL EXAMINATION:
Blood Pressure: [BP measurements]
Heart Rate: [HR and rhythm]
Cardiovascular: [Detailed cardiac exam]
Peripheral Vascular: [Peripheral circulation]
ECG: [ECG findings]
LABORATORY RESULTS:
[Cardiac markers, lipids, etc.]
IMAGING:
[Echo, stress test, etc.]
ASSESSMENT:
[Cardiac diagnosis]
PLAN:
[Cardiac management plan]
[Cardiologist Name]
[Date and Time]
""",
'structured_fields': [
'chief_complaint', 'cardiac_history', 'risk_factors',
'cardiac_medications', 'cardiac_exam', 'ecg_findings',
'lab_results', 'imaging_results', 'cardiac_assessment', 'cardiac_plan'
]
},
{
'name': 'Emergency Department Note',
'note_type': 'EMERGENCY',
'specialty': 'EMERGENCY',
'content': """
TIME OF ARRIVAL: [Arrival time]
CHIEF COMPLAINT: [Emergency complaint]
TRIAGE LEVEL: [Triage priority]
HISTORY OF PRESENT ILLNESS:
[Emergency presentation details]
PAST MEDICAL HISTORY:
[Relevant medical history]
MEDICATIONS:
[Current medications]
ALLERGIES:
[Known allergies]
PHYSICAL EXAMINATION:
General Appearance: [Patient appearance]
Vital Signs: [Emergency vital signs]
Primary Survey: [ABCDE assessment]
Secondary Survey: [Detailed examination]
EMERGENCY PROCEDURES:
[Procedures performed]
LABORATORY/IMAGING:
[Emergency testing results]
ASSESSMENT:
[Emergency diagnosis]
DISPOSITION:
[Emergency department disposition]
[Emergency Physician Name]
[Date and Time]
""",
'structured_fields': [
'arrival_time', 'chief_complaint', 'triage_level',
'emergency_history', 'emergency_exam', 'procedures_performed',
'emergency_testing', 'emergency_assessment', 'disposition'
]
},
{
'name': 'Surgical Consultation',
'note_type': 'CONSULTATION',
'specialty': 'SURGERY',
'content': """
REASON FOR CONSULTATION: [Surgical consultation reason]
HISTORY OF PRESENT ILLNESS:
[Surgical problem history]
PAST SURGICAL HISTORY:
[Previous surgeries]
MEDICATIONS:
[Current medications]
ALLERGIES:
[Known allergies]
PHYSICAL EXAMINATION:
[Surgical examination findings]
IMAGING STUDIES:
[Relevant imaging]
LABORATORY RESULTS:
[Preoperative labs]
ASSESSMENT:
[Surgical assessment]
RECOMMENDATIONS:
[Surgical recommendations]
SURGICAL PLAN:
[Proposed surgical intervention]
RISKS AND BENEFITS DISCUSSED:
[Risk discussion]
[Surgeon Name]
[Date and Time]
""",
'structured_fields': [
'consultation_reason', 'surgical_history', 'past_surgeries',
'surgical_exam', 'imaging_studies', 'preop_labs',
'surgical_assessment', 'surgical_recommendations', 'surgical_plan'
]
},
{
'name': 'Progress Note',
'note_type': 'PROGRESS',
'specialty': 'INTERNAL_MEDICINE',
'content': """
SUBJECTIVE:
[Patient's reported symptoms and concerns]
OBJECTIVE:
Vital Signs: [Current vital signs]
Physical Examination: [Current physical findings]
Laboratory Results: [Recent lab results]
Imaging Studies: [Recent imaging]
ASSESSMENT:
[Current clinical assessment]
PLAN:
[Updated treatment plan]
[Provider Name]
[Date and Time]
""",
'structured_fields': [
'subjective', 'vital_signs', 'physical_exam',
'lab_results', 'imaging', 'assessment', 'plan'
]
}
]
for tenant in tenants:
for config in template_configs:
try:
template = NoteTemplate.objects.create(
tenant=tenant,
template_id=uuid.uuid4(),
name=config['name'],
description=f"Standard {config['name'].lower()} template",
note_type=config['note_type'],
specialty=config['specialty'],
template_content=config['content'].strip(),
structured_fields=config['structured_fields'],
is_active=True,
is_default=config['name'] == 'General Consultation Note',
usage_count=random.randint(0, 100),
version=1.0,
quality_indicators=[
'completeness', 'accuracy', 'timeliness',
'clarity', 'compliance'
],
compliance_requirements=[
'documentation_complete_within_24h',
'provider_signature_required',
'patient_privacy_maintained'
],
created_at=django_timezone.now() - timedelta(days=random.randint(30, 365)),
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30))
)
templates.append(template)
except Exception as e:
print(f"Error creating template {config['name']}: {e}")
continue
print(f"Created {len(templates)} note templates")
return templates
def create_encounters(tenants, days_back=30):
"""Create patient encounters"""
encounters = []
# Get patients and providers
patients = list(PatientProfile.objects.filter(tenant__in=tenants))
if not patients:
print("No patients found. Skipping encounter creation.")
return encounters
providers = get_providers_for_emr(tenants)
if not providers:
print("No providers found. Skipping encounter creation.")
return encounters
# Get existing appointments to link with encounters
appointments = list(AppointmentRequest.objects.filter(
tenant__in=tenants,
status__in=['COMPLETED', 'IN_PROGRESS']
))
# Get existing admissions
admissions = list(Admission.objects.filter(tenant__in=tenants))
start_date = django_timezone.now().date() - timedelta(days=days_back)
# Create encounters for the past days_back days
for day_offset in range(days_back):
encounter_date = start_date + timedelta(days=day_offset)
daily_encounters = random.randint(20, 50) # Variable number of encounters per day
for _ in range(daily_encounters):
patient = random.choice(patients)
provider = random.choice([p for p in providers if p.tenant == patient.tenant])
# Determine encounter type and class
encounter_type = random.choices(
SAUDI_ENCOUNTER_TYPES,
weights=[25, 20, 10, 8, 5, 12, 8, 7, 3, 2]
)[0]
if encounter_type == 'EMERGENCY':
encounter_class = 'EMERGENCY'
elif encounter_type in ['SURGERY', 'PROCEDURE']:
encounter_class = random.choice(['INPATIENT', 'DAY_SURGERY', 'OUTPATIENT'])
elif encounter_type == 'TELEMEDICINE':
encounter_class = 'TELEMEDICINE'
else:
encounter_class = random.choice(['OUTPATIENT', 'INPATIENT'])
# Generate encounter timing
start_hour = random.randint(8, 20)
start_minute = random.choice([0, 15, 30, 45])
start_datetime = django_timezone.make_aware(
datetime.combine(encounter_date, time(start_hour, start_minute))
)
duration_minutes = random.randint(15, 180)
if encounter_type == 'SURGERY':
duration_minutes = random.randint(60, 360)
elif encounter_type == 'EMERGENCY':
duration_minutes = random.randint(30, 240)
end_datetime = start_datetime + timedelta(minutes=duration_minutes)
# Determine status
if encounter_date < django_timezone.now().date():
status = random.choices(
['COMPLETED', 'CANCELLED'],
weights=[85, 15]
)[0]
elif encounter_date == django_timezone.now().date():
status = random.choices(
['IN_PROGRESS', 'COMPLETED'],
weights=[30, 70]
)[0]
else:
status = 'SCHEDULED'
# Priority and acuity
if encounter_type == 'EMERGENCY':
priority = random.choices(['EMERGENCY', 'URGENT'], weights=[60, 40])[0]
acuity_level = random.randint(2, 5)
else:
priority = random.choices(['ROUTINE', 'URGENT'], weights=[80, 20])[0]
acuity_level = random.randint(1, 3)
# Link to appointment if available
linked_appointment = None
if appointments:
suitable_appointments = [
apt for apt in appointments
if apt.patient == patient and
apt.scheduled_datetime and
apt.scheduled_datetime.date() == encounter_date
]
if suitable_appointments:
linked_appointment = random.choice(suitable_appointments)
# Link to admission if inpatient
linked_admission = None
if encounter_class == 'INPATIENT' and admissions:
suitable_admissions = [
adm for adm in admissions
if adm.patient == patient and
adm.admission_datetime <= start_datetime
]
if suitable_admissions:
linked_admission = random.choice(suitable_admissions)
# Documentation completion
documentation_complete = status == 'COMPLETED'
signed_off = documentation_complete and random.choice([True, False])
try:
encounter = Encounter.objects.create(
tenant=patient.tenant,
encounter_id=uuid.uuid4(),
patient=patient,
provider=provider,
encounter_type=encounter_type,
encounter_class=encounter_class,
start_datetime=start_datetime,
end_datetime=end_datetime if status in ['COMPLETED', 'CANCELLED'] else None,
status=status,
location=random.choice(SAUDI_LOCATIONS),
room_number=f"Room {random.randint(101, 450)}",
appointment=linked_appointment,
admission=linked_admission,
chief_complaint=random.choice(SAUDI_CHIEF_COMPLAINTS),
reason_for_visit=random.choice(SAUDI_CHIEF_COMPLAINTS),
priority=priority,
acuity_level=acuity_level,
documentation_complete=documentation_complete,
signed_off=signed_off,
signed_by=provider if signed_off else None,
signed_datetime=end_datetime if signed_off else None,
billable=random.choice([True, False]),
billing_codes=[
f"{random.randint(99200, 99499)}",
f"{random.randint(99200, 99499)}"
] if random.choice([True, False]) else [],
quality_measures={
'documentation_score': random.randint(70, 100),
'timeliness_score': random.randint(80, 100),
'completeness_score': random.randint(75, 100)
},
created_at=start_datetime - timedelta(minutes=random.randint(5, 60)),
updated_at=django_timezone.now() - timedelta(minutes=random.randint(0, 30)),
created_by=provider
)
encounters.append(encounter)
except Exception as e:
print(f"Error creating encounter for {patient.get_full_name()}: {e}")
continue
print(f"Created {len(encounters)} encounters")
return encounters
def create_vital_signs(encounters):
"""Create vital signs for encounters"""
vital_signs = []
providers = User.objects.filter(
role__in=['NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN', 'MEDICAL_ASSISTANT'],
is_active=True
)
if not providers:
# Use encounter providers as fallback
providers = list(set([enc.provider for enc in encounters]))
for encounter in encounters:
# Create 1-3 vital signs measurements per encounter
num_measurements = random.randint(1, 3)
for measurement_num in range(num_measurements):
# Time offset for multiple measurements
time_offset = measurement_num * random.randint(30, 120) # 30-120 minutes apart
measured_datetime = encounter.start_datetime + timedelta(minutes=time_offset)
# Generate realistic vital signs based on patient age
patient_age = (django_timezone.now().date() - encounter.patient.date_of_birth).days // 365
# Temperature (Celsius)
temperature = round(random.uniform(36.0, 39.5), 1)
if temperature > 38.5:
temperature_method = random.choice(['ORAL', 'RECTAL', 'TYMPANIC'])
else:
temperature_method = random.choice(['ORAL', 'AXILLARY', 'TYMPANIC'])
# 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)
bp_position = random.choice(['SITTING', 'LYING', 'STANDING'])
bp_cuff_size = 'ADULT' if patient_age >= 18 else random.choice(['PEDIATRIC', 'INFANT'])
# 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)
heart_rhythm = random.choices(
['REGULAR', 'IRREGULAR', 'BRADYCARDIA', 'TACHYCARDIA'],
weights=[85, 10, 3, 2]
)[0]
# 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)
oxygen_delivery = 'ROOM_AIR'
oxygen_flow_rate = None
if oxygen_saturation < 95:
oxygen_delivery = random.choice(['NASAL_CANNULA', 'MASK', 'NON_REBREATHER'])
oxygen_flow_rate = random.randint(2, 15)
# Pain scale
pain_scale = random.randint(0, 10)
pain_location = None
pain_quality = None
if pain_scale > 0:
pain_location = random.choice([
'Head', 'Chest', 'Abdomen', 'Back', 'Extremities',
'Generalized', 'Neck', 'Pelvis'
])
pain_quality = random.choice([
'Sharp', 'Dull', 'Burning', 'Cramping',
'Stabbing', 'Throbbing', 'Aching'
])
# 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)
# Head circumference for pediatric patients
head_circumference = None
if patient_age < 2:
head_circumference = round(random.uniform(30.0, 50.0), 1)
# Critical values detection
critical_values = []
if temperature > 39.0 or temperature < 35.0:
critical_values.append('temperature')
if systolic_bp > 180 or systolic_bp < 90:
critical_values.append('systolic_bp')
if heart_rate > 120 or heart_rate < 50:
critical_values.append('heart_rate')
if oxygen_saturation < 90:
critical_values.append('oxygen_saturation')
alerts_generated = len(critical_values) > 0
try:
vital_sign = VitalSigns.objects.create(
encounter=encounter,
patient=encounter.patient,
measurement_id=uuid.uuid4(),
measured_datetime=measured_datetime,
temperature=Decimal(str(temperature)),
temperature_method=temperature_method,
systolic_bp=systolic_bp,
diastolic_bp=diastolic_bp,
bp_position=bp_position,
bp_cuff_size=bp_cuff_size,
heart_rate=heart_rate,
heart_rhythm=heart_rhythm,
respiratory_rate=respiratory_rate,
oxygen_saturation=oxygen_saturation,
oxygen_delivery=oxygen_delivery,
oxygen_flow_rate=oxygen_flow_rate,
pain_scale=pain_scale,
pain_location=pain_location,
pain_quality=pain_quality,
weight=Decimal(str(weight)),
height=Decimal(str(height)),
head_circumference=Decimal(str(head_circumference)) if head_circumference else None,
device_used=random.choice(['MANUAL', 'AUTOMATED', 'MONITOR']),
device_calibrated=random.choice([True, False]),
measured_by=random.choice(providers),
verified_by=random.choice(providers) if random.choice([True, False]) else None,
critical_values=critical_values,
alerts_generated=alerts_generated,
notes=f"Routine vital signs measurement #{measurement_num + 1}" if measurement_num > 0 else None,
created_at=measured_datetime,
updated_at=measured_datetime + timedelta(minutes=random.randint(1, 10))
)
vital_signs.append(vital_sign)
except Exception as e:
print(f"Error creating vital signs for encounter {encounter.encounter_id}: {e}")
continue
print(f"Created {len(vital_signs)} vital signs records")
return vital_signs
def create_problem_lists(patients, providers):
"""Create problem lists for patients"""
problems = []
for patient in patients:
# Each patient has 1-5 problems
num_problems = random.randint(1, 5)
for _ in range(num_problems):
problem_data = random.choice(SAUDI_COMMON_PROBLEMS)
problem_name, problem_code, coding_system = problem_data
problem_type = random.choices(
SAUDI_PROBLEM_TYPES,
weights=[60, 20, 10, 5, 2, 2, 1]
)[0]
# Onset date (random date in the past)
onset_date = django_timezone.now().date() - timedelta(
days=random.randint(30, 3650) # 1 month to 10 years ago
)
# Severity
severity = random.choices(
['MILD', 'MODERATE', 'SEVERE', 'CRITICAL'],
weights=[40, 35, 20, 5]
)[0]
# Priority
priority = random.choices(
['HIGH', 'MEDIUM', 'LOW'],
weights=[20, 60, 20]
)[0]
# Status
status = random.choices(
['ACTIVE', 'RESOLVED', 'INACTIVE', 'CHRONIC'],
weights=[50, 20, 10, 20]
)[0]
# Resolution date for resolved problems
resolution_date = None
resolution_notes = None
if status == 'RESOLVED':
resolution_date = onset_date + timedelta(
days=random.randint(7, 365)
)
resolution_notes = f"Problem resolved after treatment. Patient improved significantly."
# Provider assignments
diagnosing_provider = random.choice([p for p in providers if p.tenant == patient.tenant])
managing_provider = diagnosing_provider if random.choice([True, False]) else random.choice(
[p for p in providers if p.tenant == patient.tenant])
# Fix timezone issue
verified_date = None
if random.choice([True, False]):
verified_date = django_timezone.now()
try:
problem = ProblemList.objects.create(
tenant=patient.tenant,
patient=patient,
problem_id=uuid.uuid4(),
problem_name=problem_name,
problem_code=problem_code,
coding_system=coding_system,
problem_type=problem_type,
onset_date=onset_date,
onset_description=f"Patient reports {problem_name.lower()} started gradually",
severity=severity,
priority=priority,
status=status,
resolution_date=resolution_date,
resolution_notes=resolution_notes,
diagnosing_provider=diagnosing_provider,
managing_provider=managing_provider,
body_site=random.choice([
'Chest', 'Abdomen', 'Head', 'Extremities', 'Back', 'Neck'
]) if random.choice([True, False]) else None,
laterality=random.choice(['LEFT', 'RIGHT', 'BILATERAL']) if random.choice([True, False]) else None,
clinical_notes=f"Patient diagnosed with {problem_name}. {severity.lower()} severity. Status: {status.lower()}.",
patient_concerns=f"Patient concerned about {problem_name.lower()} impact on daily activities",
treatment_goals=[
f"Manage {problem_name.lower()} symptoms",
"Improve quality of life",
"Prevent complications"
],
outcome_measures=[
"Symptom improvement",
"Functional status",
"Patient satisfaction"
],
verified=random.choice([True, False]),
verified_by=managing_provider if random.choice([True, False]) else None,
verified_date=verified_date, # Use timezone-aware datetime
created_at=django_timezone.make_aware(
datetime.combine(onset_date, time(random.randint(8, 17), random.randint(0, 59)))
),
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 30)),
created_by=diagnosing_provider
)
problems.append(problem)
except Exception as e:
print(f"Error creating problem for {patient.get_full_name()}: {e}")
continue
print(f"Created {len(problems)} problem list entries")
return problems
def create_care_plans(patients, providers, problems):
"""Create care plans for patients"""
care_plans = []
for patient in patients:
# Each patient has 1-2 care plans
num_plans = random.randint(1, 2)
patient_problems = [p for p in problems if p.patient == patient]
if not patient_problems:
continue
for _ in range(num_plans):
plan_type = random.choice(SAUDI_CARE_PLAN_TYPES)
# Select related problems for this care plan
related_problems_list = random.sample(
patient_problems,
min(random.randint(1, 3), len(patient_problems))
)
# Generate care plan title and description
main_problem = related_problems_list[0].problem_name
title = f"{plan_type.replace('_', ' ').title()} Plan for {main_problem}"
description = f"Comprehensive {plan_type.lower().replace('_', ' ')} plan for managing {main_problem} and related conditions."
# Care plan category
category = random.choice([
'MEDICAL', 'SURGICAL', 'NURSING', 'REHABILITATION',
'PREVENTIVE', 'CHRONIC_DISEASE', 'ACUTE_CARE'
])
# Timeline
start_date = django_timezone.now().date()
end_date = start_date + timedelta(days=random.randint(30, 365))
target_completion_date = end_date - timedelta(days=random.randint(0, 30))
# Status
status = random.choices(
['ACTIVE', 'COMPLETED', 'ON_HOLD', 'CANCELLED'],
weights=[60, 25, 10, 5]
)[0]
# Priority
priority = random.choices(
['HIGH', 'MEDIUM', 'LOW'],
weights=[25, 50, 25]
)[0]
# Providers
primary_provider = random.choice([p for p in providers if p.tenant == patient.tenant])
care_team_list = random.sample(
[p for p in providers if p.tenant == patient.tenant],
min(random.randint(2, 4), len([p for p in providers if p.tenant == patient.tenant]))
)
# Goals and objectives
goals = [
f"Manage {main_problem} effectively",
"Improve patient quality of life",
"Prevent disease progression",
"Optimize medication therapy"
]
objectives = [
f"Reduce {main_problem.lower()} symptoms by 50%",
"Achieve target vital signs",
"Improve functional status",
"Enhance patient understanding"
]
# Interventions and activities
interventions = [
"Medication management",
"Patient education",
"Lifestyle modifications",
"Regular monitoring"
]
activities = [
"Daily medication review",
"Weekly patient assessment",
"Monthly provider consultation",
"Quarterly outcome evaluation"
]
# Completion percentage
completion_percentage = random.randint(0, 100) if status in ['ACTIVE', 'COMPLETED'] else 0
if status == 'COMPLETED':
completion_percentage = 100
# Approval
approved = random.choice([True, False])
approved_date = django_timezone.now() if approved else None
try:
care_plan = CarePlan.objects.create(
tenant=patient.tenant,
patient=patient,
care_plan_id=uuid.uuid4(),
title=title,
description=description,
plan_type=plan_type,
category=category,
start_date=start_date,
end_date=end_date,
target_completion_date=target_completion_date,
status=status,
priority=priority,
primary_provider=primary_provider,
goals=goals,
objectives=objectives,
interventions=interventions,
activities=activities,
monitoring_parameters=[
"Vital signs",
"Symptom severity",
"Functional status",
"Medication adherence"
],
evaluation_criteria=[
"Symptom improvement",
"Quality of life measures",
"Patient satisfaction",
"Clinical outcomes"
],
patient_goals=[
"Feel better",
"Return to normal activities",
"Understand my condition",
"Take medications correctly"
],
patient_preferences=[
"Prefer oral medications",
"Morning appointments preferred",
"Family involvement desired"
],
patient_barriers=[
"Transportation challenges",
"Language barriers",
"Financial constraints"
] if random.choice([True, False]) else [],
resources_needed=[
"Medication",
"Medical equipment",
"Educational materials",
"Support services"
],
support_systems=[
"Family support",
"Healthcare team",
"Community resources"
],
progress_notes=f"Patient progressing well with {plan_type.lower().replace('_', ' ')} plan.",
last_reviewed=django_timezone.now().date() - timedelta(days=random.randint(0, 30)),
next_review_date=django_timezone.now().date() + timedelta(days=random.randint(7, 90)),
outcomes_achieved=[
"Symptom reduction achieved",
"Patient education completed"
] if completion_percentage > 50 else [],
completion_percentage=completion_percentage,
approved=approved,
approved_by=primary_provider if approved else None,
approved_date=approved_date, # Use timezone-aware datetime
created_at=django_timezone.make_aware(
datetime.combine(start_date, time(random.randint(8, 17), random.randint(0, 59)))
),
updated_at=django_timezone.now() - timedelta(days=random.randint(0, 7)),
created_by=primary_provider
)
# Set many-to-many relationships after creation
care_plan.care_team.set(care_team_list)
care_plan.related_problems.set(related_problems_list)
care_plans.append(care_plan)
except Exception as e:
print(f"Error creating care plan for {patient.get_full_name()}: {e}")
continue
print(f"Created {len(care_plans)} care plans")
return care_plans
def create_clinical_notes(encounters, templates):
"""Create clinical notes for encounters"""
clinical_notes = []
for encounter in encounters:
if encounter.status not in ['COMPLETED', 'IN_PROGRESS']:
continue
# Each encounter has 1-2 clinical notes
num_notes = random.randint(1, 2)
for note_num in range(num_notes):
# Select appropriate template
suitable_templates = [
t for t in templates
if t.tenant == encounter.tenant and (
t.note_type == encounter.encounter_type or
t.specialty == 'FAMILY_MEDICINE'
)
]
template = random.choice(suitable_templates) if suitable_templates else None
# Note type based on encounter
note_type = encounter.encounter_type if encounter.encounter_type in [nt for nt in
SAUDI_NOTE_TYPES] else 'CONSULTATION'
# Generate note title
titles = {
'CONSULTATION': f"Consultation Note - {encounter.chief_complaint}",
'EMERGENCY': f"Emergency Department Note - {encounter.chief_complaint}",
'PROGRESS': f"Progress Note - Day {note_num + 1}",
'SURGERY': f"Surgical Note - {encounter.chief_complaint}",
'PROCEDURE': f"Procedure Note - {encounter.chief_complaint}",
'DISCHARGE': f"Discharge Summary - {encounter.chief_complaint}"
}
title = titles.get(note_type, f"Clinical Note - {encounter.chief_complaint}")
# Generate note content
if template:
content = template.template_content
# Replace placeholders with actual data
content = content.replace('[Chief Complaint]', encounter.chief_complaint)
content = content.replace('[Provider Name]', encounter.provider.get_full_name())
content = content.replace('[Date and Time]', encounter.start_datetime.strftime('%Y-%m-%d %H:%M'))
content = content.replace('[History details]',
f"Patient presents with {encounter.chief_complaint.lower()}.")
content = content.replace('[Clinical impression]',
f"Impression consistent with {encounter.chief_complaint.lower()}.")
content = content.replace('[Treatment plan]',
"Continue current management plan and follow up as scheduled.")
else:
content = f"""
ENCOUNTER DATE: {encounter.start_datetime.strftime('%Y-%m-%d %H:%M')}
PATIENT: {encounter.patient.get_full_name()}
PROVIDER: {encounter.provider.get_full_name()}
CHIEF COMPLAINT: {encounter.chief_complaint}
HISTORY OF PRESENT ILLNESS:
Patient presents with {encounter.chief_complaint.lower()}. Symptoms have been present for several days.
PHYSICAL EXAMINATION:
Patient appears comfortable. Vital signs stable.
ASSESSMENT:
Clinical impression consistent with {encounter.chief_complaint.lower()}.
PLAN:
Continue current management. Follow up in clinic as needed.
Electronically signed by: {encounter.provider.get_full_name()}
Date: {encounter.start_datetime.strftime('%Y-%m-%d %H:%M')}
""".strip()
# Note timing
note_datetime = encounter.start_datetime + timedelta(minutes=random.randint(15, 120))
# Status and signing
if encounter.status == 'COMPLETED':
note_status = random.choices(
['SIGNED', 'PENDING_SIGNATURE', 'DRAFT'],
weights=[70, 20, 10]
)[0]
else:
note_status = 'DRAFT'
electronically_signed = note_status == 'SIGNED'
signed_datetime = note_datetime + timedelta(
minutes=random.randint(30, 180)) if electronically_signed else None
# Co-signers list
co_signers_list = []
if random.choice([True, False]): # 50% chance of co-signer
potential_cosigners = [
p for p in User.objects.filter(
tenant=encounter.tenant,
role__in=['PHYSICIAN', 'NURSE_PRACTITIONER'],
is_active=True
) if p != encounter.provider
]
if potential_cosigners:
co_signers_list = [random.choice(potential_cosigners)]
# Quality and compliance
quality_score = random.randint(75, 100)
compliance_flags = []
if quality_score < 85:
compliance_flags = ['documentation_incomplete', 'signature_pending']
try:
clinical_note = ClinicalNote.objects.create(
encounter=encounter,
patient=encounter.patient,
note_id=uuid.uuid4(),
note_type=note_type,
title=title,
content=content,
template=template,
structured_data={
'chief_complaint': encounter.chief_complaint,
'encounter_type': encounter.encounter_type,
'provider': encounter.provider.get_full_name(),
'location': encounter.location
},
author=encounter.provider,
status=note_status,
electronically_signed=electronically_signed,
signed_datetime=signed_datetime,
signature_method='ELECTRONIC' if electronically_signed else None,
quality_score=quality_score,
compliance_flags=compliance_flags,
note_datetime=note_datetime,
confidential=random.choice([True, False]),
restricted_access=random.choice([True, False]) if random.random() < 0.1 else False,
access_restrictions=['PROVIDER_ONLY'] if random.choice([True, False]) else [],
created_at=note_datetime,
updated_at=note_datetime + timedelta(minutes=random.randint(5, 60))
)
# Set many-to-many relationship after creation
clinical_note.co_signers.set(co_signers_list)
clinical_notes.append(clinical_note)
except Exception as e:
print(f"Error creating clinical note for encounter {encounter.encounter_id}: {e}")
continue
print(f"Created {len(clinical_notes)} clinical notes")
return clinical_notes
def main():
"""Main function to generate all EMR data"""
print("Starting Saudi Healthcare EMR Data Generation...")
# Get existing tenants
tenants = list(Tenant.objects.all())
if not tenants:
print("❌ No tenants found. Please run the core data generator first.")
return
# Create note templates
print("\n1. Creating Note Templates...")
templates = create_note_templates(tenants)
# Create encounters
print("\n2. Creating Patient Encounters...")
encounters = create_encounters(tenants, days_back=30)
# Create vital signs
print("\n3. Creating Vital Signs...")
vital_signs = create_vital_signs(encounters)
# Get patients and providers for remaining models
patients = list(PatientProfile.objects.filter(tenant__in=tenants))
providers = get_providers_for_emr(tenants)
# Create problem lists
print("\n4. Creating Problem Lists...")
problems = create_problem_lists(patients, providers)
# Create care plans
print("\n5. Creating Care Plans...")
care_plans = create_care_plans(patients, providers, problems)
# Create clinical notes
print("\n6. Creating Clinical Notes...")
clinical_notes = create_clinical_notes(encounters, templates)
print(f"\n✅ Saudi Healthcare EMR Data Generation Complete!")
print(f"📊 Summary:")
print(f" - Note Templates: {len(templates)}")
print(f" - Patient Encounters: {len(encounters)}")
print(f" - Vital Signs Records: {len(vital_signs)}")
print(f" - Problem List Entries: {len(problems)}")
print(f" - Care Plans: {len(care_plans)}")
print(f" - Clinical Notes: {len(clinical_notes)}")
# Statistics
if encounters:
encounter_type_counts = {}
for encounter in encounters:
encounter_type_counts[encounter.encounter_type] = encounter_type_counts.get(encounter.encounter_type, 0) + 1
print(f"\n📈 Encounter Type Distribution:")
for enc_type, count in sorted(encounter_type_counts.items(), key=lambda x: x[1], reverse=True)[:10]:
print(f" - {enc_type.replace('_', ' ').title()}: {count}")
if problems:
problem_status_counts = {}
for problem in problems:
problem_status_counts[problem.status] = problem_status_counts.get(problem.status, 0) + 1
print(f"\n📋 Problem Status Distribution:")
for status, count in problem_status_counts.items():
print(f" - {status.title()}: {count}")
return {
'templates': templates,
'encounters': encounters,
'vital_signs': vital_signs,
'problems': problems,
'care_plans': care_plans,
'clinical_notes': clinical_notes
}
if __name__ == "__main__":
main()