617 lines
28 KiB
Python
617 lines
28 KiB
Python
import os
|
||
import django
|
||
|
||
# Setup Django (keep or remove if you convert this to a management command)
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
|
||
django.setup()
|
||
|
||
import random
|
||
import uuid
|
||
from datetime import datetime, timedelta, date, time as dt_time
|
||
from django.utils import timezone
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
|
||
from inpatients.models import Ward, Bed, Admission, Transfer, DischargeSummary
|
||
from accounts.models import User
|
||
from patients.models import PatientProfile
|
||
from core.models import Tenant
|
||
|
||
# Facility management FKs
|
||
from facility_management.models import Building, Floor, Asset
|
||
|
||
# Optional: Operating Room (if your app exists); otherwise we’ll leave OR unset
|
||
try:
|
||
from operating_theatre.models import OperatingRoom
|
||
except Exception:
|
||
OperatingRoom = None
|
||
|
||
# ------------------------------
|
||
# Saudi-inspired constants
|
||
# ------------------------------
|
||
SAUDI_WARD_NAMES = [
|
||
'King Fahd Ward', 'King Abdulaziz Ward', 'Prince Sultan Ward', 'Princess Noura Ward',
|
||
'Al-Faisal Ward', 'Al-Rashid Ward', 'Al-Salam Ward', 'Al-Noor Ward',
|
||
'Al-Shifa Ward', 'Al-Amal Ward', 'Al-Rahma Ward', 'Al-Baraka Ward',
|
||
'Cardiac Care Unit', 'Intensive Care Unit', 'Pediatric ICU', 'Neonatal ICU',
|
||
'Medical Ward A', 'Medical Ward B', 'Surgical Ward A', 'Surgical Ward B',
|
||
'Orthopedic Ward', 'Maternity Ward', 'Pediatric Ward', 'Oncology Ward'
|
||
]
|
||
|
||
SAUDI_SPECIALTIES = [
|
||
Ward.WardSpecialty.GENERAL_MEDICINE, Ward.WardSpecialty.SURGERY,
|
||
Ward.WardSpecialty.CARDIOLOGY, Ward.WardSpecialty.NEUROLOGY,
|
||
Ward.WardSpecialty.ONCOLOGY, Ward.WardSpecialty.PEDIATRICS,
|
||
Ward.WardSpecialty.OBSTETRICS, Ward.WardSpecialty.GYNECOLOGY,
|
||
Ward.WardSpecialty.ORTHOPEDICS, Ward.WardSpecialty.PSYCHIATRY,
|
||
Ward.WardSpecialty.EMERGENCY, Ward.WardSpecialty.CRITICAL_CARE,
|
||
Ward.WardSpecialty.REHABILITATION, Ward.WardSpecialty.OTHER
|
||
]
|
||
|
||
SAUDI_WINGS = ['North Wing', 'South Wing', 'East Wing', 'West Wing', 'Central Wing', 'VIP Wing', 'Family Wing', 'Isolation Wing']
|
||
|
||
SAUDI_COMMON_DIAGNOSES = [
|
||
'Diabetes Mellitus Type 2', 'Hypertension', 'Coronary Artery Disease',
|
||
'Chronic Kidney Disease', 'Heart Failure', 'Pneumonia', 'Gastritis',
|
||
'Cholecystitis', 'Appendicitis', 'Urinary Tract Infection',
|
||
'Acute Myocardial Infarction', 'Stroke', 'Pulmonary Embolism',
|
||
'Deep Vein Thrombosis', 'Sepsis', 'Acute Renal Failure',
|
||
'Chronic Obstructive Pulmonary Disease', 'Asthma', 'Anemia'
|
||
]
|
||
|
||
SAUDI_SURGICAL_PROCEDURES = [
|
||
'Coronary Artery Bypass Graft', 'Percutaneous Coronary Intervention',
|
||
'Appendectomy', 'Cholecystectomy', 'Hernia Repair', 'Hip Replacement',
|
||
'Knee Replacement', 'Cataract Surgery', 'Thyroidectomy',
|
||
'Prostatectomy', 'Hysterectomy', 'Cesarean Section',
|
||
'Arthroscopy', 'Endoscopy', 'Colonoscopy', 'Angioplasty'
|
||
]
|
||
|
||
SAUDI_MEDICATIONS = [
|
||
'Metformin', 'Insulin', 'Lisinopril', 'Amlodipine', 'Atorvastatin',
|
||
'Aspirin', 'Warfarin', 'Furosemide', 'Omeprazole', 'Paracetamol',
|
||
'Amoxicillin', 'Ciprofloxacin', 'Prednisolone', 'Morphine', 'Tramadol'
|
||
]
|
||
|
||
# ------------------------------
|
||
# Helpers
|
||
# ------------------------------
|
||
|
||
def random_ratio_value():
|
||
# return nurse-to-patient ratio as float (e.g., 1:4 -> 0.25)
|
||
patients_per_nurse = random.choice([4, 5, 6, 8])
|
||
return round(1.0 / patients_per_nurse, 3)
|
||
|
||
def pick_tenant_users(tenant, roles=None, limit=20):
|
||
qs = User.objects.filter(tenant=tenant, is_active=True)
|
||
if roles:
|
||
qs = qs.filter(employee_profile__role__in=roles)
|
||
return list(qs[:limit])
|
||
|
||
def ensure_building_and_floors(tenant, floors_count=5):
|
||
"""
|
||
Ensure at least 1 building with a few floors exists for the tenant.
|
||
Returns (building, floors_list)
|
||
"""
|
||
bld = Building.objects.filter(tenant=tenant).first()
|
||
if not bld:
|
||
# Minimal building compatible with your facility_management.Building model
|
||
bld = Building.objects.create(
|
||
tenant=tenant,
|
||
name=f"{tenant.name} Medical Tower",
|
||
code=f"T{tenant.id}-BLD-01",
|
||
address=f"{tenant.name} Campus, KSA",
|
||
building_type=Building.BuildingType.CLINICAL
|
||
)
|
||
floors = list(Floor.objects.filter(building=bld).order_by('floor_number'))
|
||
if not floors:
|
||
for n in range(floors_count):
|
||
floors.append(Floor.objects.create(
|
||
building=bld,
|
||
floor_number=n,
|
||
name=f"Level {n}",
|
||
))
|
||
return bld, floors
|
||
|
||
def any_building_floor(tenant):
|
||
b = Building.objects.filter(tenant=tenant).order_by('?').first()
|
||
if not b:
|
||
b, _floors = ensure_building_and_floors(tenant)
|
||
f = Floor.objects.filter(building=b).order_by('?').first()
|
||
if not f:
|
||
# create one more floor
|
||
f = Floor.objects.create(building=b, floor_number=0, name="Level 0")
|
||
return b, f
|
||
|
||
def friendly_phone():
|
||
return f"+966-11-{random.randint(100, 999)}-{random.randint(1000, 9999)}"
|
||
|
||
def ward_id_for(tenant):
|
||
# unique per (tenant, ward_id)
|
||
return f"WRD-{timezone.now().strftime('%Y%m%d')}-{random.randint(1000,9999)}"
|
||
|
||
# ------------------------------
|
||
# Ward creation (FK to Building/Floor)
|
||
# ------------------------------
|
||
|
||
def create_saudi_wards(tenants, wards_per_tenant=12):
|
||
wards = []
|
||
for tenant in tenants:
|
||
# Ensure at least one building/floor exists
|
||
building, floor = any_building_floor(tenant)
|
||
|
||
nurses = pick_tenant_users(tenant, roles=['NURSE', 'NURSE_PRACTITIONER'], limit=30)
|
||
physicians = pick_tenant_users(tenant, roles=['PHYSICIAN', 'PHYSICIAN_ASSISTANT'], limit=30)
|
||
created_by = (nurses or physicians or pick_tenant_users(tenant, None, 1) or [None])[0]
|
||
|
||
for _ in range(wards_per_tenant):
|
||
name = random.choice(SAUDI_WARD_NAMES)
|
||
# Make name unique within tenant if needed
|
||
orig = name
|
||
suffix = 1
|
||
while Ward.objects.filter(tenant=tenant, name=name).exists():
|
||
name = f"{orig} {suffix}"
|
||
suffix += 1
|
||
|
||
# Choose building/floor (random per ward)
|
||
bld, flr = any_building_floor(tenant)
|
||
|
||
total_beds = random.randint(20, 60)
|
||
private_rooms = random.randint(5, 15)
|
||
semi_private_rooms = random.randint(8, 20)
|
||
shared_rooms = max(0, total_beds - private_rooms - (semi_private_rooms * 2))
|
||
|
||
ward = Ward.objects.create(
|
||
tenant=tenant,
|
||
ward_id=ward_id_for(tenant),
|
||
name=name,
|
||
description=f"Specialized ward providing comprehensive inpatient care",
|
||
ward_type=random.choice([w[0] for w in Ward.WardType.choices]),
|
||
specialty=random.choice(SAUDI_SPECIALTIES),
|
||
total_beds=total_beds,
|
||
private_rooms=private_rooms,
|
||
semi_private_rooms=semi_private_rooms,
|
||
shared_rooms=shared_rooms,
|
||
building=bld,
|
||
floor=flr,
|
||
wing=random.choice(SAUDI_WINGS),
|
||
nurse_manager=(random.choice(nurses) if nurses else None),
|
||
min_nurses_day=random.randint(8, 15),
|
||
min_nurses_night=random.randint(5, 10),
|
||
nurse_to_patient_ratio=random_ratio_value(),
|
||
equipment_list=[
|
||
'Cardiac Monitors', 'IV Pumps', 'Ventilators', 'Defibrillators',
|
||
'Oxygen Supply', 'Suction Units', 'Patient Beds', 'Wheelchairs'
|
||
],
|
||
special_features=[
|
||
'Prayer Room', 'Family Waiting Area', 'Isolation Rooms',
|
||
'Nurse Station', 'Clean Utility Room', 'Dirty Utility Room'
|
||
],
|
||
admission_criteria="Patients requiring specialized inpatient care",
|
||
age_restrictions=random.choice([
|
||
{"min_age": 0, "max_age": None},
|
||
{"min_age": 16, "max_age": None},
|
||
{"min_age": 0, "max_age": 18},
|
||
]),
|
||
gender_restrictions=random.choice([g[0] for g in Ward.GenderRestriction.choices]),
|
||
is_active=True,
|
||
is_accepting_admissions=random.choice([True, True, True, False]),
|
||
closure_reason=None,
|
||
phone_number=friendly_phone(),
|
||
extension=f"{random.randint(1000, 9999)}",
|
||
created_by=created_by
|
||
)
|
||
|
||
# Attending physicians
|
||
if physicians:
|
||
ward.attending_physicians.set(random.sample(physicians, k=min(len(physicians), random.randint(2, 5))))
|
||
|
||
wards.append(ward)
|
||
|
||
print(f"Created {wards_per_tenant} wards for {tenant.name}")
|
||
return wards
|
||
|
||
# ------------------------------
|
||
# Beds (FK to Ward) + equipment M2M to Assets in same building
|
||
# ------------------------------
|
||
|
||
def create_saudi_beds(wards):
|
||
beds = []
|
||
for ward in wards:
|
||
bed_types = [b[0] for b in Bed.BedType.choices]
|
||
room_types = [r[0] for r in Bed.RoomType.choices]
|
||
statuses = [s[0] for s in Bed.BedStatus.choices]
|
||
cleaning_levels = [c[0] for c in Bed.CleaningLevel.choices]
|
||
positions = [p[0] for p in Bed.BedPosition.choices]
|
||
|
||
# Nearby facility assets (optional)
|
||
assets_qs = Asset.objects.filter(building=ward.building)
|
||
ward_floor_num = ward.floor.floor_number if hasattr(ward.floor, "floor_number") else 0
|
||
|
||
for num in range(1, ward.total_beds + 1):
|
||
bed_number = f"{ward.name[:3].upper()}-{num:03d}"
|
||
room_type = random.choice(room_types)
|
||
status = random.choice(statuses)
|
||
|
||
# Room number: floor digit + random
|
||
room_number = f"{ward_floor_num}{random.randint(10, 99)}"
|
||
|
||
# Bed position logic based on room type
|
||
if room_type == Bed.RoomType.SHARED:
|
||
pos_choices = positions[:4] # A-D
|
||
elif room_type == Bed.RoomType.SEMI_PRIVATE:
|
||
pos_choices = positions[:2] # A-B
|
||
else:
|
||
pos_choices = [positions[0]] # A
|
||
bed_position = random.choice(pos_choices)
|
||
|
||
bed = Bed.objects.create(
|
||
ward=ward,
|
||
bed_number=bed_number,
|
||
room_number=room_number,
|
||
bed_type=random.choice(bed_types),
|
||
is_operational=True,
|
||
is_active=True,
|
||
is_out_of_service=False,
|
||
room_type=room_type,
|
||
status=status,
|
||
current_admission=None,
|
||
occupied_since=(timezone.now() - timedelta(days=random.randint(1, 30))) if status == 'OCCUPIED' else None,
|
||
reserved_until=(timezone.now() + timedelta(hours=random.randint(1, 24))) if (status == 'RESERVED' and random.choice([True, False])) else None,
|
||
features=[
|
||
'Adjustable Height', 'Side Rails', 'Trendelenburg', 'Reverse Trendelenburg',
|
||
'Patient Controls', 'LED Reading Light'
|
||
],
|
||
last_maintenance=timezone.now() - timedelta(days=random.randint(7, 90)),
|
||
next_maintenance=timezone.now() + timedelta(days=random.randint(30, 180)),
|
||
maintenance_notes="Routine maintenance completed" if random.choice([True, False]) else None,
|
||
last_cleaned=timezone.now() - timedelta(hours=random.randint(1, 24)),
|
||
cleaned_by=None,
|
||
cleaning_level=random.choice(cleaning_levels),
|
||
blocked_reason="Maintenance required" if status == 'BLOCKED' else None,
|
||
blocked_by=None,
|
||
blocked_until=(timezone.now() + timedelta(hours=random.randint(2, 48))) if status == 'BLOCKED' else None,
|
||
bed_position=bed_position,
|
||
created_by=ward.created_by
|
||
)
|
||
|
||
# Attach up to 2 assets in same building
|
||
if assets_qs.exists() and random.random() < 0.6:
|
||
bed.equipment.add(*assets_qs.order_by('?')[:random.randint(1, 2)])
|
||
|
||
beds.append(bed)
|
||
|
||
print(f"Created {len(beds)} beds across all wards")
|
||
return beds
|
||
|
||
# ------------------------------
|
||
# Admissions (uses enums + FKs) and sets bed occupancy correctly
|
||
# ------------------------------
|
||
|
||
def create_saudi_admissions(tenants, beds, admissions_per_tenant=100):
|
||
admissions = []
|
||
|
||
for tenant in tenants:
|
||
patients = list(PatientProfile.objects.filter(tenant=tenant))
|
||
physicians = pick_tenant_users(tenant, roles=['PHYSICIAN', 'PHYSICIAN_ASSISTANT'], limit=50)
|
||
nurses = pick_tenant_users(tenant, roles=['NURSE', 'NURSE_PRACTITIONER'], limit=50)
|
||
tenant_beds = [b for b in beds if b.ward.tenant_id == tenant.id]
|
||
|
||
if not patients or not physicians or not tenant_beds:
|
||
print(f"⚠️ Skipping tenant {tenant.name}: patients={len(patients)}, physicians={len(physicians)}, beds={len(tenant_beds)}")
|
||
continue
|
||
|
||
creator = (physicians or nurses or [None])[0]
|
||
|
||
for _ in range(admissions_per_tenant):
|
||
patient = random.choice(patients)
|
||
admitting_physician = random.choice(physicians)
|
||
attending_physician = random.choice(physicians)
|
||
current_bed = random.choice(tenant_beds)
|
||
current_ward = current_bed.ward
|
||
|
||
admission_dt = timezone.now() - timedelta(days=random.randint(0, 30))
|
||
status = random.choice([s[0] for s in Admission.AdmissionStatus.choices])
|
||
|
||
discharge_dt = None
|
||
discharge_disp = None
|
||
if status == Admission.AdmissionStatus.DISCHARGED:
|
||
discharge_dt = admission_dt + timedelta(days=random.randint(1, 14))
|
||
discharge_disp = random.choice([d[0] for d in Admission.DischargeDisposition.choices])
|
||
|
||
admission = Admission.objects.create(
|
||
tenant=tenant,
|
||
patient=patient,
|
||
admission_datetime=admission_dt,
|
||
admission_type=random.choice([t[0] for t in Admission.AdmissionType.choices]),
|
||
admission_source=random.choice([s[0] for s in Admission.AdmissionSource.choices]),
|
||
chief_complaint=random.choice([
|
||
'Chest pain', 'Shortness of breath', 'Abdominal pain',
|
||
'Fever and chills', 'Weakness', 'Nausea and vomiting',
|
||
'Headache', 'Dizziness', 'Joint pain', 'Back pain'
|
||
]),
|
||
admitting_diagnosis=random.choice(SAUDI_COMMON_DIAGNOSES),
|
||
secondary_diagnoses=[random.choice(SAUDI_COMMON_DIAGNOSES) for _ in range(random.randint(0, 3))],
|
||
admitting_physician=admitting_physician,
|
||
attending_physician=attending_physician,
|
||
current_ward=current_ward,
|
||
current_bed=current_bed,
|
||
status=status,
|
||
priority=random.choice([p[0] for p in Admission.AdmissionPriority.choices]),
|
||
acuity_level=random.randint(1, 5),
|
||
insurance_verified=random.choice([True, False]),
|
||
authorization_number=(f"AUTH-{random.randint(100000, 999999)}" if random.choice([True, False]) else None),
|
||
estimated_length_of_stay=random.randint(2, 14),
|
||
discharge_planning_started=random.choice([True, False]),
|
||
discharge_planner=(random.choice(nurses) if nurses and random.random() < 0.5 else None),
|
||
anticipated_discharge_date=admission_dt.date() + timedelta(days=random.randint(1, 10)),
|
||
discharge_datetime=discharge_dt,
|
||
discharge_disposition=discharge_disp,
|
||
isolation_required=random.choice([True, False, False, False]),
|
||
isolation_type=(random.choice([i[0] for i in Admission.IsolationType.choices]) if random.random() < 0.25 else None),
|
||
special_needs=(['Interpreter services', 'Dietary restrictions', 'Mobility assistance'] if random.random() < 0.3 else []),
|
||
allergies=(random.sample(['Penicillin', 'Sulfa drugs', 'Shellfish', 'Latex', 'Contrast dye'], random.randint(0, 3))),
|
||
alerts=(random.sample(['Fall risk', 'Suicide risk', 'Flight risk', 'Combative', 'Infection control', 'Allergy alert'], random.randint(0, 3))),
|
||
code_status=random.choice([c[0] for c in Admission.CodeStatus.choices]),
|
||
advance_directive=random.choice([True, False]),
|
||
healthcare_proxy=(None if random.random() < 0.7 else "Next of kin on file"),
|
||
admission_notes=f"Admitted with {random.choice(SAUDI_COMMON_DIAGNOSES).lower()} requiring inpatient management",
|
||
created_by=creator
|
||
)
|
||
|
||
# Consulting physicians
|
||
if physicians:
|
||
admission.consulting_physicians.set(random.sample(physicians, random.randint(0, min(3, len(physicians)))))
|
||
|
||
# Update bed status according to admission status
|
||
if status in [Admission.AdmissionStatus.ADMITTED, Admission.AdmissionStatus.TRANSFERRED]:
|
||
current_bed.status = Bed.BedStatus.OCCUPIED
|
||
current_bed.current_admission = admission
|
||
current_bed.occupied_since = admission_dt
|
||
current_bed.reserved_until = None
|
||
current_bed.save()
|
||
elif status == Admission.AdmissionStatus.PENDING:
|
||
# reserve a pending bed sometimes
|
||
if random.random() < 0.5:
|
||
current_bed.status = Bed.BedStatus.RESERVED
|
||
current_bed.reserved_until = admission_dt + timedelta(hours=random.randint(2, 12))
|
||
current_bed.save()
|
||
|
||
admissions.append(admission)
|
||
|
||
print(f"Created {admissions_per_tenant} admissions for {tenant.name}")
|
||
return admissions
|
||
|
||
# ------------------------------
|
||
# Transfers (matches enums, timestamps; requested_datetime is auto_now_add)
|
||
# ------------------------------
|
||
|
||
def create_saudi_transfers(admissions):
|
||
transfers = []
|
||
|
||
for admission in random.sample(admissions, int(len(admissions) * 0.3) or 0):
|
||
# Find an available bed in a different ward of same tenant
|
||
available_beds = Bed.objects.filter(
|
||
ward__tenant=admission.tenant,
|
||
status=Bed.BedStatus.AVAILABLE
|
||
).exclude(ward=admission.current_ward)
|
||
|
||
if not available_beds.exists():
|
||
continue
|
||
|
||
to_bed = available_beds.order_by('?').first()
|
||
to_ward = to_bed.ward
|
||
|
||
# Requesting user (prefer clinical roles)
|
||
requesters = pick_tenant_users(admission.tenant,
|
||
roles=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT'],
|
||
limit=50) or pick_tenant_users(admission.tenant, None, 5)
|
||
if not requesters:
|
||
continue
|
||
requested_by = random.choice(requesters)
|
||
|
||
status = random.choice([s[0] for s in Transfer.TransferStatus.choices])
|
||
scheduled_datetime = timezone.now() + timedelta(hours=random.randint(1, 12)) if status in ['APPROVED', 'SCHEDULED', 'IN_PROGRESS', 'COMPLETED'] else None
|
||
actual_datetime = scheduled_datetime + timedelta(minutes=random.randint(5, 60)) if status in ['IN_PROGRESS', 'COMPLETED'] else None
|
||
|
||
transfer = Transfer.objects.create(
|
||
admission=admission,
|
||
patient=admission.patient,
|
||
transfer_type=random.choice([t[0] for t in Transfer.TransferType.choices]),
|
||
from_ward=admission.current_ward,
|
||
from_bed=admission.current_bed,
|
||
to_ward=to_ward,
|
||
to_bed=to_bed,
|
||
scheduled_datetime=scheduled_datetime,
|
||
actual_datetime=actual_datetime,
|
||
status=status,
|
||
reason=random.choice([
|
||
'Clinical deterioration', 'Need for higher level of care',
|
||
'Bed availability', 'Specialist consultation required',
|
||
'Patient preference', 'Isolation requirements'
|
||
]),
|
||
priority=random.choice([p[0] for p in Transfer.TransferPriority.choices]),
|
||
requested_by=requested_by,
|
||
approved_by=None,
|
||
completed_by=None,
|
||
transport_method=random.choice([m[0] for m in Transfer.TransportMethod.choices]),
|
||
equipment_needed=(['IV pole', 'Oxygen tank', 'Monitor'] if random.random() < 0.4 else []),
|
||
supplies_needed=(['Medications', 'Patient chart', 'Personal items'] if random.random() < 0.4 else []),
|
||
patient_condition=random.choice([pc[0] for pc in Transfer.TransferPatientCondition.choices]),
|
||
vital_signs={
|
||
'blood_pressure': f"{random.randint(90, 180)}/{random.randint(60, 100)}",
|
||
'heart_rate': random.randint(60, 120),
|
||
'respiratory_rate': random.randint(12, 24),
|
||
'temperature': round(random.uniform(36.0, 39.0), 1),
|
||
'oxygen_saturation': random.randint(92, 100)
|
||
},
|
||
handoff_report=f"Handoff given to receiving nurse; patient for {random.choice(['specialized care', 'higher acuity monitoring', 'isolation'])}",
|
||
medications_transferred=random.sample(SAUDI_MEDICATIONS, random.randint(0, 4)),
|
||
delay_reason=("Bed not ready" if status in ['APPROVED', 'SCHEDULED'] and random.random() < 0.3 else None),
|
||
complications=("None noted" if status == 'COMPLETED' else None),
|
||
notes=(f"Transfer completed successfully" if status == 'COMPLETED' else None)
|
||
)
|
||
|
||
# Transport team
|
||
nurses = pick_tenant_users(admission.tenant, roles=['NURSE', 'NURSE_PRACTITIONER'], limit=20)
|
||
if nurses:
|
||
transfer.transport_team.set(random.sample(nurses, k=min(len(nurses), random.randint(1, 3))))
|
||
|
||
# If completed, update bed occupancy and admission location
|
||
if status == 'COMPLETED':
|
||
# free from_bed
|
||
if transfer.from_bed:
|
||
transfer.from_bed.status = Bed.BedStatus.AVAILABLE
|
||
transfer.from_bed.current_admission = None
|
||
transfer.from_bed.occupied_since = None
|
||
transfer.from_bed.save()
|
||
|
||
# occupy to_bed
|
||
to_bed.status = Bed.BedStatus.OCCUPIED
|
||
to_bed.current_admission = admission
|
||
to_bed.occupied_since = actual_datetime or timezone.now()
|
||
to_bed.save()
|
||
|
||
# update admission location
|
||
admission.current_ward = to_ward
|
||
admission.current_bed = to_bed
|
||
admission.save(update_fields=['current_ward', 'current_bed'])
|
||
|
||
transfers.append(transfer)
|
||
|
||
print(f"Created {len(transfers)} transfers")
|
||
return transfers
|
||
|
||
# ------------------------------
|
||
# Discharge Summaries (match choices/fields)
|
||
# ------------------------------
|
||
|
||
def create_saudi_discharge_summaries(admissions):
|
||
summaries = []
|
||
|
||
discharged = [a for a in admissions if a.status == Admission.AdmissionStatus.DISCHARGED and a.discharge_datetime]
|
||
for admission in discharged:
|
||
los = (admission.discharge_datetime.date() - admission.admission_datetime.date()).days
|
||
discharging_physician = admission.attending_physician
|
||
|
||
summary = DischargeSummary.objects.create(
|
||
admission=admission,
|
||
discharge_date=admission.discharge_datetime.date(),
|
||
discharge_time=admission.discharge_datetime.time(),
|
||
length_of_stay=max(los, 0),
|
||
admission_diagnosis=admission.admitting_diagnosis,
|
||
final_diagnosis=random.choice(SAUDI_COMMON_DIAGNOSES),
|
||
secondary_diagnoses=admission.secondary_diagnoses or [],
|
||
procedures_performed=random.sample(SAUDI_SURGICAL_PROCEDURES, random.randint(0, 3)),
|
||
hospital_course=f"Admitted with {admission.admitting_diagnosis.lower()} and responded well to treatment.",
|
||
complications=(None if random.random() < 0.7 else "Minor wound infection, resolved with antibiotics"),
|
||
discharge_medications=random.sample(SAUDI_MEDICATIONS, random.randint(3, 8)),
|
||
medication_changes=(None if random.random() < 0.5 else "Adjusted antihypertensives and insulin dosing."),
|
||
activity_restrictions=random.choice([
|
||
None, "No lifting >10 kg for 2 weeks", "Bedrest x 24 hours", "Activity as tolerated"
|
||
]),
|
||
diet_instructions=random.choice([
|
||
None, "Diabetic diet", "Low sodium diet", "Cardiac diet", "Regular diet"
|
||
]),
|
||
wound_care=(None if random.random() < 0.6 else "Keep incision clean and dry. Change dressing daily."),
|
||
special_instructions=(None if random.random() < 0.5 else "Monitor blood pressure and blood sugar at home."),
|
||
follow_up_appointments=[
|
||
{"provider": "Primary Care Physician", "date": (admission.discharge_datetime.date() + timedelta(days=7)).isoformat(), "time": "10:00"},
|
||
{"provider": "Cardiologist", "date": (admission.discharge_datetime.date() + timedelta(days=14)).isoformat(), "time": "14:00"}
|
||
],
|
||
follow_up_instructions="Follow up with PCP in 1 week and specialist in 2 weeks.",
|
||
warning_signs="Fever >38.5°C, chest pain, worsening shortness of breath.",
|
||
when_to_call="Call physician or return to ED if any warning signs occur.",
|
||
discharge_disposition=(admission.discharge_disposition or 'HOME'),
|
||
discharge_location=("Home" if (admission.discharge_disposition or 'HOME') == 'HOME' else "Rehabilitation facility"),
|
||
transportation_arranged=random.choice([True, False]),
|
||
transportation_method=random.choice([t[0] for t in DischargeSummary.TransportationMethod.choices]),
|
||
durable_medical_equipment=(random.sample(['Walker', 'Hospital bed', 'Oxygen concentrator'], random.randint(0, 2))),
|
||
supplies_provided=(random.sample(['Wound care supplies', 'Medications', 'Educational materials'], random.randint(0, 3))),
|
||
education_provided=['Disease management', 'Medication administration', 'Signs and symptoms to report'],
|
||
education_materials=['Discharge instructions', 'Medication list', 'Educational brochures'],
|
||
patient_understanding=random.choice([p[0] for p in DischargeSummary.PatientUnderstanding.choices]),
|
||
discharge_planner=(admission.discharge_planner or None),
|
||
social_worker_involved=random.choice([True, False]),
|
||
case_manager_involved=random.choice([True, False]),
|
||
readmission_risk=random.choice([r[0] for r in DischargeSummary.ReadmissionRisk.choices]),
|
||
patient_satisfaction=random.randint(6, 10), # 1–10 scale
|
||
discharging_physician=discharging_physician,
|
||
primary_nurse=None,
|
||
summary_completed=True,
|
||
summary_signed=True,
|
||
patient_copy_provided=True,
|
||
created_by=discharging_physician
|
||
)
|
||
summaries.append(summary)
|
||
|
||
print(f"Created {len(summaries)} discharge summaries")
|
||
return summaries
|
||
|
||
# ------------------------------
|
||
# Main
|
||
# ------------------------------
|
||
|
||
def main():
|
||
print("Starting Saudi Inpatients Data Generation (refactored for updated models)…")
|
||
|
||
tenants = list(Tenant.objects.all())
|
||
if not tenants:
|
||
print("❌ No tenants found. Seed core/tenants first.")
|
||
return
|
||
|
||
# 1) Wards
|
||
print("\n1. Creating Wards …")
|
||
wards = create_saudi_wards(tenants, wards_per_tenant=10)
|
||
|
||
# 2) Beds
|
||
print("\n2. Creating Beds …")
|
||
beds = create_saudi_beds(wards)
|
||
|
||
# 3) Admissions
|
||
print("\n3. Creating Admissions …")
|
||
admissions = create_saudi_admissions(tenants, beds, admissions_per_tenant=80)
|
||
|
||
# 4) Transfers
|
||
print("\n4. Creating Transfers …")
|
||
transfers = create_saudi_transfers(admissions)
|
||
|
||
# 5) Discharge Summaries
|
||
print("\n5. Creating Discharge Summaries …")
|
||
summaries = create_saudi_discharge_summaries(admissions)
|
||
|
||
print("\n✅ Done.")
|
||
print(f"📊 Summary:")
|
||
print(f" - Wards: {len(wards)}")
|
||
print(f" - Beds: {len(beds)}")
|
||
print(f" - Admissions: {len(admissions)}")
|
||
print(f" - Transfers: {len(transfers)}")
|
||
print(f" - Discharge Summaries: {len(summaries)}")
|
||
|
||
# Status distribution (nice labels)
|
||
from collections import Counter
|
||
st = Counter([a.status for a in admissions])
|
||
print("\n🏥 Admission Status Distribution:")
|
||
for k, v in st.items():
|
||
print(f" - {k}: {v}")
|
||
|
||
# Nurse-to-patient ratios used
|
||
print("\n👩⚕️ Nurse-to-Patient Ratios Used:")
|
||
seen = set()
|
||
for w in wards:
|
||
if w.nurse_to_patient_ratio and w.nurse_to_patient_ratio > 0:
|
||
p = int(round(1 / w.nurse_to_patient_ratio))
|
||
seen.add(f"1:{p}")
|
||
for r in sorted(seen):
|
||
print(f" - {r}")
|
||
|
||
return {
|
||
'wards': wards,
|
||
'beds': beds,
|
||
'admissions': admissions,
|
||
'transfers': transfers,
|
||
'summaries': summaries
|
||
}
|
||
|
||
if __name__ == "__main__":
|
||
main()
|