1724 lines
47 KiB
Python
1724 lines
47 KiB
Python
"""
|
|
Inpatients app models for hospital management system.
|
|
Provides ward management, bed allocation, admissions, transfers, and discharge planning.
|
|
"""
|
|
|
|
import uuid
|
|
from django.db import models
|
|
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
|
|
from django.utils import timezone
|
|
from django.conf import settings
|
|
from datetime import timedelta, datetime, time
|
|
import json
|
|
|
|
from kombu.transport.redis import PRIORITY_STEPS
|
|
|
|
|
|
class Ward(models.Model):
|
|
"""
|
|
Hospital ward model for organizing patient care areas.
|
|
"""
|
|
WARD_TYPE_CHOICES = [
|
|
('GENERAL', 'General Medical'),
|
|
('SURGICAL', 'Surgical'),
|
|
('ICU', 'Intensive Care Unit'),
|
|
('CCU', 'Cardiac Care Unit'),
|
|
('NICU', 'Neonatal ICU'),
|
|
('PICU', 'Pediatric ICU'),
|
|
('EMERGENCY', 'Emergency'),
|
|
('MATERNITY', 'Maternity'),
|
|
('PEDIATRIC', 'Pediatric'),
|
|
('ONCOLOGY', 'Oncology'),
|
|
('CARDIAC', 'Cardiac'),
|
|
('ORTHOPEDIC', 'Orthopedic'),
|
|
('NEUROLOGY', 'Neurology'),
|
|
('PSYCHIATRY', 'Psychiatry'),
|
|
('REHABILITATION', 'Rehabilitation'),
|
|
('ISOLATION', 'Isolation'),
|
|
('STEP_DOWN', 'Step Down'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
SPECIALITY_CHOICES = [
|
|
('GENERAL_MEDICINE', 'General Medicine'),
|
|
('SURGERY', 'Surgery'),
|
|
('CARDIOLOGY', 'Cardiology'),
|
|
('NEUROLOGY', 'Neurology'),
|
|
('ONCOLOGY', 'Oncology'),
|
|
('PEDIATRICS', 'Pediatrics'),
|
|
('OBSTETRICS', 'Obstetrics'),
|
|
('GYNECOLOGY', 'Gynecology'),
|
|
('ORTHOPEDICS', 'Orthopedics'),
|
|
('PSYCHIATRY', 'Psychiatry'),
|
|
('EMERGENCY', 'Emergency Medicine'),
|
|
('CRITICAL_CARE', 'Critical Care'),
|
|
('REHABILITATION', 'Rehabilitation'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
GENDER_RESTRICTIONS_CHOICES = [
|
|
('NONE', 'No Restrictions'),
|
|
('MALE_ONLY', 'Male Only'),
|
|
('FEMALE_ONLY', 'Female Only'),
|
|
]
|
|
# Tenant relationship
|
|
tenant = models.ForeignKey(
|
|
'core.Tenant',
|
|
on_delete=models.CASCADE,
|
|
related_name='wards',
|
|
help_text='Organization tenant'
|
|
)
|
|
|
|
# Ward Information
|
|
ward_id = models.CharField(
|
|
max_length=50,
|
|
help_text='Unique ward identifier'
|
|
)
|
|
name = models.CharField(
|
|
max_length=200,
|
|
help_text='Ward name'
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Ward description'
|
|
)
|
|
|
|
# Ward Type and Specialty
|
|
ward_type = models.CharField(
|
|
max_length=50,
|
|
choices=WARD_TYPE_CHOICES,
|
|
help_text='Type of ward'
|
|
)
|
|
|
|
specialty = models.CharField(
|
|
max_length=100,
|
|
choices=SPECIALITY_CHOICES,
|
|
help_text='Medical specialty'
|
|
)
|
|
|
|
# Capacity and Configuration
|
|
total_beds = models.PositiveIntegerField(
|
|
help_text='Total number of beds in ward'
|
|
)
|
|
private_rooms = models.PositiveIntegerField(
|
|
default=0,
|
|
help_text='Number of private rooms'
|
|
)
|
|
semi_private_rooms = models.PositiveIntegerField(
|
|
default=0,
|
|
help_text='Number of semi-private rooms'
|
|
)
|
|
shared_rooms = models.PositiveIntegerField(
|
|
default=0,
|
|
help_text='Number of shared rooms'
|
|
)
|
|
|
|
# Location Information
|
|
building = models.CharField(
|
|
max_length=100,
|
|
help_text='Building name or identifier'
|
|
)
|
|
floor = models.CharField(
|
|
max_length=20,
|
|
help_text='Floor number or identifier'
|
|
)
|
|
wing = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Wing or section'
|
|
)
|
|
|
|
# Staffing Information
|
|
nurse_manager = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='managed_wards',
|
|
help_text='Nurse manager for this ward'
|
|
)
|
|
attending_physicians = models.ManyToManyField(
|
|
settings.AUTH_USER_MODEL,
|
|
related_name='attending_wards',
|
|
blank=True,
|
|
help_text='Attending physicians for this ward'
|
|
)
|
|
|
|
# Staffing Requirements
|
|
min_nurses_day = models.PositiveIntegerField(
|
|
default=1,
|
|
help_text='Minimum nurses required for day shift'
|
|
)
|
|
min_nurses_night = models.PositiveIntegerField(
|
|
default=1,
|
|
help_text='Minimum nurses required for night shift'
|
|
)
|
|
nurse_to_patient_ratio = models.FloatField(
|
|
default=1.0,
|
|
help_text='Nurse to patient ratio'
|
|
)
|
|
|
|
# Equipment and Features
|
|
equipment_list = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Available equipment in ward'
|
|
)
|
|
special_features = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Special features and capabilities'
|
|
)
|
|
|
|
# Admission Criteria
|
|
admission_criteria = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Admission criteria for this ward'
|
|
)
|
|
age_restrictions = models.JSONField(
|
|
default=dict,
|
|
blank=True,
|
|
help_text='Age restrictions (min_age, max_age)'
|
|
)
|
|
gender_restrictions = models.CharField(
|
|
max_length=20,
|
|
choices=GENDER_RESTRICTIONS_CHOICES,
|
|
default='NONE',
|
|
help_text='Gender restrictions'
|
|
)
|
|
|
|
# Status and Operations
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Ward is active and operational'
|
|
)
|
|
is_accepting_admissions = models.BooleanField(
|
|
default=True,
|
|
help_text='Ward is accepting new admissions'
|
|
)
|
|
closure_reason = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Reason for closure if not accepting admissions'
|
|
)
|
|
|
|
# Contact Information
|
|
phone_number = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Ward phone number'
|
|
)
|
|
extension = models.CharField(
|
|
max_length=10,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Phone extension'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_wards',
|
|
help_text='User who created the ward'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_ward'
|
|
verbose_name = 'Ward'
|
|
verbose_name_plural = 'Wards'
|
|
ordering = ['building', 'floor', 'name']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'ward_type']),
|
|
models.Index(fields=['specialty']),
|
|
models.Index(fields=['is_active', 'is_accepting_admissions']),
|
|
]
|
|
unique_together = ['tenant', 'ward_id']
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def occupancy_rate(self):
|
|
"""
|
|
Calculate current occupancy rate.
|
|
"""
|
|
occupied_beds = self.beds.filter(status='OCCUPIED').count()
|
|
if self.total_beds == 0:
|
|
return 0.0
|
|
return 100 * (occupied_beds / self.total_beds)
|
|
|
|
|
|
@property
|
|
def available_beds(self):
|
|
"""
|
|
Get count of available beds.
|
|
"""
|
|
return self.beds.filter(status='AVAILABLE').count()
|
|
|
|
@property
|
|
def occupied_beds(self):
|
|
return self.beds.filter(status='OCCUPIED').count()
|
|
|
|
|
|
class Bed(models.Model):
|
|
"""
|
|
Hospital bed model for tracking individual bed status and assignments.
|
|
"""
|
|
BED_TYPE_CHOICES = [
|
|
('STANDARD', 'Standard Bed'),
|
|
('ICU', 'ICU Bed'),
|
|
('CARDIAC', 'Cardiac Monitoring'),
|
|
('ISOLATION', 'Isolation Bed'),
|
|
('BARIATRIC', 'Bariatric Bed'),
|
|
('PEDIATRIC', 'Pediatric Bed'),
|
|
('NEONATAL', 'Neonatal Bed'),
|
|
('MATERNITY', 'Maternity Bed'),
|
|
('PSYCHIATRIC', 'Psychiatric Bed'),
|
|
('STRETCHER', 'Stretcher/Gurney'),
|
|
('RECLINER', 'Recliner Chair'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
ROOM_TYPE_CHOICES = [
|
|
('PRIVATE', 'Private Room'),
|
|
('SEMI_PRIVATE', 'Semi-Private'),
|
|
('SHARED', 'Shared Room'),
|
|
('ICU', 'ICU Room'),
|
|
('ISOLATION', 'Isolation Room'),
|
|
]
|
|
STATUS_CHOICES = [
|
|
('AVAILABLE', 'Available'),
|
|
('OCCUPIED', 'Occupied'),
|
|
('RESERVED', 'Reserved'),
|
|
('MAINTENANCE', 'Under Maintenance'),
|
|
('CLEANING', 'Being Cleaned'),
|
|
('OUT_OF_ORDER', 'Out of Order'),
|
|
('BLOCKED', 'Blocked'),
|
|
]
|
|
CLEANING_LEVEL_CHOICES = [
|
|
('STANDARD', 'Standard Cleaning'),
|
|
('DEEP', 'Deep Cleaning'),
|
|
('ISOLATION', 'Isolation Cleaning'),
|
|
('TERMINAL', 'Terminal Cleaning'),
|
|
]
|
|
BED_POSITION_CHOICES = [
|
|
('A', 'A'),
|
|
('B', 'B'),
|
|
('C', 'C'),
|
|
('D', 'D'),
|
|
('E', 'E'),
|
|
('F', 'F'),
|
|
]
|
|
# Ward relationship
|
|
ward = models.ForeignKey(
|
|
Ward,
|
|
on_delete=models.CASCADE,
|
|
related_name='beds',
|
|
help_text='Ward containing this bed'
|
|
)
|
|
|
|
# Bed Information
|
|
bed_number = models.CharField(
|
|
max_length=20,
|
|
help_text='Bed number or identifier'
|
|
)
|
|
room_number = models.CharField(
|
|
max_length=20,
|
|
help_text='Room number'
|
|
)
|
|
|
|
# Bed Type and Configuration
|
|
bed_type = models.CharField(
|
|
max_length=30,
|
|
choices=BED_TYPE_CHOICES,
|
|
default='STANDARD',
|
|
help_text='Type of bed'
|
|
)
|
|
is_operational = models.BooleanField(
|
|
default=True,
|
|
help_text='Operational status'
|
|
)
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text='Active status'
|
|
)
|
|
is_active_out_of_service = models.BooleanField(
|
|
default=True,
|
|
)
|
|
room_type = models.CharField(
|
|
max_length=20,
|
|
choices=ROOM_TYPE_CHOICES,
|
|
help_text='Type of room'
|
|
)
|
|
|
|
# Bed Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='AVAILABLE',
|
|
help_text='Current bed status'
|
|
)
|
|
|
|
# Current Assignment
|
|
current_patient = models.ForeignKey(
|
|
'patients.PatientProfile',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='current_bed',
|
|
help_text='Currently assigned patient'
|
|
)
|
|
current_admission = models.ForeignKey(
|
|
'Admission',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='assigned_bed',
|
|
help_text='Current admission using this bed'
|
|
)
|
|
|
|
# Assignment Timing
|
|
occupied_since = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='When bed became occupied'
|
|
)
|
|
reserved_until = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Reservation expiry time'
|
|
)
|
|
|
|
# Equipment and Features
|
|
equipment = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Equipment available with this bed'
|
|
)
|
|
features = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Special features of this bed'
|
|
)
|
|
|
|
# Maintenance Information
|
|
last_maintenance = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Last maintenance date'
|
|
)
|
|
next_maintenance = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Next scheduled maintenance'
|
|
)
|
|
maintenance_notes = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Maintenance notes and history'
|
|
)
|
|
|
|
# Cleaning Information
|
|
last_cleaned = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Last cleaning timestamp'
|
|
)
|
|
cleaned_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='cleaned_beds',
|
|
help_text='Staff member who last cleaned bed'
|
|
)
|
|
cleaning_level = models.CharField(
|
|
max_length=20,
|
|
choices=CLEANING_LEVEL_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Level of last cleaning'
|
|
)
|
|
|
|
# Blocking Information
|
|
blocked_reason = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Reason for blocking bed'
|
|
)
|
|
blocked_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='blocked_beds',
|
|
help_text='User who blocked the bed'
|
|
)
|
|
blocked_until = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Blocked until this time'
|
|
)
|
|
|
|
# Location Details
|
|
bed_position = models.CharField(
|
|
max_length=20,
|
|
choices=BED_POSITION_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Position within room'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_beds',
|
|
help_text='User who created the bed record'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_bed'
|
|
verbose_name = 'Bed'
|
|
verbose_name_plural = 'Beds'
|
|
ordering = ['ward', 'room_number', 'bed_number']
|
|
indexes = [
|
|
models.Index(fields=['ward', 'status']),
|
|
models.Index(fields=['current_patient']),
|
|
models.Index(fields=['bed_type', 'room_type']),
|
|
models.Index(fields=['status']),
|
|
]
|
|
unique_together = ['ward', 'bed_number']
|
|
|
|
def __str__(self):
|
|
return f"{self.ward.name} - Room {self.room_number}, Bed {self.bed_number}"
|
|
|
|
@property
|
|
def is_available(self):
|
|
"""
|
|
Check if bed is available for assignment.
|
|
"""
|
|
return self.status == 'AVAILABLE'
|
|
|
|
@property
|
|
def occupancy_duration(self):
|
|
"""
|
|
Calculate how long bed has been occupied.
|
|
"""
|
|
if self.occupied_since:
|
|
return timezone.now() - self.occupied_since
|
|
return None
|
|
|
|
|
|
class Admission(models.Model):
|
|
"""
|
|
Patient admission model for tracking inpatient stays.
|
|
"""
|
|
ADMISSION_TYPE_CHOICES = [
|
|
('EMERGENCY', 'Emergency Admission'),
|
|
('ELECTIVE', 'Elective Admission'),
|
|
('URGENT', 'Urgent Admission'),
|
|
('OBSERVATION', 'Observation'),
|
|
('DAY_SURGERY', 'Day Surgery'),
|
|
('TRANSFER', 'Transfer from Another Facility'),
|
|
('READMISSION', 'Readmission'),
|
|
('DIRECT', 'Direct Admission'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
ADMISSION_SOURCE_CHOICES = [
|
|
('EMERGENCY', 'Emergency Department'),
|
|
('OUTPATIENT', 'Outpatient Clinic'),
|
|
('PHYSICIAN_OFFICE', 'Physician Office'),
|
|
('TRANSFER', 'Transfer from Another Hospital'),
|
|
('NURSING_HOME', 'Nursing Home'),
|
|
('HOME', 'Home'),
|
|
('AMBULATORY_SURGERY', 'Ambulatory Surgery'),
|
|
('OTHER_FACILITY', 'Other Healthcare Facility'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
STATUS_CHOICES = [
|
|
('PENDING', 'Pending Admission'),
|
|
('ADMITTED', 'Admitted'),
|
|
('TRANSFERRED', 'Transferred'),
|
|
('DISCHARGED', 'Discharged'),
|
|
('DECEASED', 'Deceased'),
|
|
('LEFT_AMA', 'Left Against Medical Advice'),
|
|
('CANCELLED', 'Cancelled'),
|
|
]
|
|
PRIORITY_CHOICES = [
|
|
('ROUTINE', 'Routine'),
|
|
('URGENT', 'Urgent'),
|
|
('EMERGENT', 'Emergent'),
|
|
('CRITICAL', 'Critical'),
|
|
]
|
|
DISCHARGE_DISPOSITION_CHOICES = [
|
|
('HOME', 'Home'),
|
|
('HOME_HEALTH', 'Home with Health Services'),
|
|
('NURSING_HOME', 'Nursing Home'),
|
|
('REHAB_FACILITY', 'Rehabilitation Facility'),
|
|
('HOSPICE', 'Hospice'),
|
|
('TRANSFER', 'Transfer to Another Hospital'),
|
|
('DECEASED', 'Deceased'),
|
|
('LEFT_AMA', 'Left Against Medical Advice'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
ISOLATION_TYPE_CHOICES = [
|
|
('CONTACT', 'Contact Precautions'),
|
|
('DROPLET', 'Droplet Precautions'),
|
|
('AIRBORNE', 'Airborne Precautions'),
|
|
('PROTECTIVE', 'Protective Isolation'),
|
|
('STRICT', 'Strict Isolation'),
|
|
]
|
|
CODE_STATUS_CHOICES = [
|
|
('FULL_CODE', 'Full Code'),
|
|
('DNR', 'Do Not Resuscitate'),
|
|
('DNI', 'Do Not Intubate'),
|
|
('DNR_DNI', 'DNR/DNI'),
|
|
('COMFORT_CARE', 'Comfort Care Only'),
|
|
('LIMITED', 'Limited Code'),
|
|
]
|
|
# Tenant relationship
|
|
tenant = models.ForeignKey(
|
|
'core.Tenant',
|
|
on_delete=models.CASCADE,
|
|
related_name='admissions',
|
|
help_text='Organization tenant'
|
|
)
|
|
|
|
# Admission Information
|
|
admission_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique admission identifier'
|
|
)
|
|
|
|
# Patient Information
|
|
patient = models.ForeignKey(
|
|
'patients.PatientProfile',
|
|
on_delete=models.CASCADE,
|
|
related_name='admissions',
|
|
help_text='Admitted patient'
|
|
)
|
|
|
|
# Admission Details
|
|
admission_datetime = models.DateTimeField(
|
|
help_text='Date and time of admission'
|
|
)
|
|
admission_type = models.CharField(
|
|
max_length=30,
|
|
choices=ADMISSION_TYPE_CHOICES,
|
|
help_text='Type of admission'
|
|
)
|
|
|
|
admission_source = models.CharField(
|
|
max_length=30,
|
|
choices=ADMISSION_SOURCE_CHOICES,
|
|
help_text='Source of admission'
|
|
)
|
|
|
|
# Clinical Information
|
|
chief_complaint = models.TextField(
|
|
help_text='Chief complaint or reason for admission'
|
|
)
|
|
admitting_diagnosis = models.TextField(
|
|
help_text='Admitting diagnosis'
|
|
)
|
|
secondary_diagnoses = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Secondary diagnoses'
|
|
)
|
|
|
|
# Provider Information
|
|
admitting_physician = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='admitted_patients',
|
|
help_text='Admitting physician'
|
|
)
|
|
attending_physician = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='attending_patients',
|
|
help_text='Attending physician'
|
|
)
|
|
consulting_physicians = models.ManyToManyField(
|
|
settings.AUTH_USER_MODEL,
|
|
related_name='consulting_patients',
|
|
blank=True,
|
|
help_text='Consulting physicians'
|
|
)
|
|
|
|
# Ward and Bed Assignment
|
|
current_ward = models.ForeignKey(
|
|
Ward,
|
|
on_delete=models.CASCADE,
|
|
related_name='current_admissions',
|
|
help_text='Current ward assignment'
|
|
)
|
|
current_bed = models.ForeignKey(
|
|
Bed,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='current_admissions',
|
|
help_text='Current bed assignment'
|
|
)
|
|
|
|
# Admission Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='PENDING',
|
|
help_text='Current admission status'
|
|
)
|
|
|
|
# Priority and Acuity
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=PRIORITY_CHOICES,
|
|
default='ROUTINE',
|
|
help_text='Admission priority'
|
|
)
|
|
acuity_level = models.PositiveIntegerField(
|
|
default=1,
|
|
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
|
help_text='Patient acuity level (1-5, 5 being highest)'
|
|
)
|
|
|
|
# Insurance and Financial
|
|
insurance_verified = models.BooleanField(
|
|
default=False,
|
|
help_text='Insurance coverage verified'
|
|
)
|
|
authorization_number = models.CharField(
|
|
max_length=100,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Insurance authorization number'
|
|
)
|
|
estimated_length_of_stay = models.PositiveIntegerField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Estimated length of stay in days'
|
|
)
|
|
|
|
# Discharge Planning
|
|
discharge_planning_started = models.BooleanField(
|
|
default=False,
|
|
help_text='Discharge planning has been initiated'
|
|
)
|
|
discharge_planner = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='discharge_planning_cases',
|
|
help_text='Assigned discharge planner'
|
|
)
|
|
anticipated_discharge_date = models.DateField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Anticipated discharge date'
|
|
)
|
|
|
|
# Discharge Information
|
|
discharge_datetime = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual discharge date and time'
|
|
)
|
|
discharge_disposition = models.CharField(
|
|
max_length=30,
|
|
choices=DISCHARGE_DISPOSITION_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Discharge disposition'
|
|
)
|
|
|
|
# Special Requirements
|
|
isolation_required = models.BooleanField(
|
|
default=False,
|
|
help_text='Patient requires isolation precautions'
|
|
)
|
|
isolation_type = models.CharField(
|
|
max_length=30,
|
|
choices=ISOLATION_TYPE_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Type of isolation required'
|
|
)
|
|
|
|
special_needs = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Special care needs and requirements'
|
|
)
|
|
|
|
# Allergies and Alerts
|
|
allergies = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Patient allergies'
|
|
)
|
|
alerts = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Clinical alerts and warnings'
|
|
)
|
|
|
|
# Code Status
|
|
code_status = models.CharField(
|
|
max_length=20,
|
|
choices=CODE_STATUS_CHOICES,
|
|
default='FULL_CODE',
|
|
help_text='Code status'
|
|
)
|
|
|
|
# Advance Directives
|
|
advance_directive = models.BooleanField(
|
|
default=False,
|
|
help_text='Advance directive on file'
|
|
)
|
|
healthcare_proxy = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Healthcare proxy information'
|
|
)
|
|
|
|
# Notes
|
|
admission_notes = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Admission notes'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_admissions',
|
|
help_text='User who created the admission'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_admission'
|
|
verbose_name = 'Admission'
|
|
verbose_name_plural = 'Admissions'
|
|
ordering = ['-admission_datetime']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'status']),
|
|
models.Index(fields=['patient', 'status']),
|
|
models.Index(fields=['current_ward']),
|
|
models.Index(fields=['admission_datetime']),
|
|
models.Index(fields=['attending_physician']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.patient.get_full_name()} - {self.admission_datetime.strftime('%Y-%m-%d')}"
|
|
|
|
@property
|
|
def length_of_stay(self):
|
|
"""
|
|
Calculate current or total length of stay.
|
|
"""
|
|
end_time = self.discharge_datetime or timezone.now()
|
|
return (end_time - self.admission_datetime).days
|
|
|
|
@property
|
|
def is_active(self):
|
|
"""
|
|
Check if admission is currently active.
|
|
"""
|
|
return self.status in ['PENDING', 'ADMITTED', 'TRANSFERRED']
|
|
|
|
|
|
class Transfer(models.Model):
|
|
"""
|
|
Patient transfer model for tracking ward/bed changes.
|
|
"""
|
|
TRANSFER_TYPE_CHOICES = [
|
|
('WARD', 'Ward Transfer'),
|
|
('BED', 'Bed Transfer'),
|
|
('ROOM', 'Room Transfer'),
|
|
('UNIT', 'Unit Transfer'),
|
|
('FACILITY', 'Facility Transfer'),
|
|
]
|
|
STATUS_CHOICES = [
|
|
('REQUESTED', 'Requested'),
|
|
('APPROVED', 'Approved'),
|
|
('SCHEDULED', 'Scheduled'),
|
|
('IN_PROGRESS', 'In Progress'),
|
|
('COMPLETED', 'Completed'),
|
|
('CANCELLED', 'Cancelled'),
|
|
('DELAYED', 'Delayed'),
|
|
]
|
|
PRIORITY_CHOICES = [
|
|
('ROUTINE', 'Routine'),
|
|
('URGENT', 'Urgent'),
|
|
('EMERGENT', 'Emergent'),
|
|
('STAT', 'STAT'),
|
|
]
|
|
TRANSPORT_METHOD_CHOICES = [
|
|
('WHEELCHAIR', 'Wheelchair'),
|
|
('STRETCHER', 'Stretcher'),
|
|
('BED', 'Hospital Bed'),
|
|
('AMBULATORY', 'Walking'),
|
|
('AMBULANCE', 'Ambulance'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
PATIENT_CONDITION_CHOICES = [
|
|
('STABLE', 'Stable'),
|
|
('UNSTABLE', 'Unstable'),
|
|
('CRITICAL', 'Critical'),
|
|
('IMPROVING', 'Improving'),
|
|
('DETERIORATING', 'Deteriorating'),
|
|
]
|
|
|
|
# Transfer Information
|
|
transfer_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique transfer identifier'
|
|
)
|
|
|
|
# Admission and Patient
|
|
admission = models.ForeignKey(
|
|
Admission,
|
|
on_delete=models.CASCADE,
|
|
related_name='transfers',
|
|
help_text='Associated admission'
|
|
)
|
|
patient = models.ForeignKey(
|
|
'patients.PatientProfile',
|
|
on_delete=models.CASCADE,
|
|
related_name='transfers',
|
|
help_text='Patient being transferred'
|
|
)
|
|
|
|
# Transfer Details
|
|
transfer_type = models.CharField(
|
|
max_length=20,
|
|
choices=TRANSFER_TYPE_CHOICES,
|
|
help_text='Type of transfer'
|
|
)
|
|
|
|
# Source Location
|
|
from_ward = models.ForeignKey(
|
|
Ward,
|
|
on_delete=models.CASCADE,
|
|
related_name='transfers_from',
|
|
help_text='Source ward'
|
|
)
|
|
from_bed = models.ForeignKey(
|
|
Bed,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='transfers_from',
|
|
help_text='Source bed'
|
|
)
|
|
|
|
# Destination Location
|
|
to_ward = models.ForeignKey(
|
|
Ward,
|
|
on_delete=models.CASCADE,
|
|
related_name='transfers_to',
|
|
help_text='Destination ward'
|
|
)
|
|
to_bed = models.ForeignKey(
|
|
Bed,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='transfers_to',
|
|
help_text='Destination bed'
|
|
)
|
|
|
|
# Transfer Timing
|
|
requested_datetime = models.DateTimeField(
|
|
auto_now_add=True,
|
|
help_text='When transfer was requested'
|
|
)
|
|
scheduled_datetime = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Scheduled transfer time'
|
|
)
|
|
actual_datetime = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual transfer time'
|
|
)
|
|
|
|
# Transfer Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='REQUESTED',
|
|
help_text='Transfer status'
|
|
)
|
|
|
|
# Transfer Reason and Priority
|
|
reason = models.TextField(
|
|
help_text='Reason for transfer'
|
|
)
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=PRIORITY_CHOICES,
|
|
default='ROUTINE',
|
|
help_text='Transfer priority'
|
|
)
|
|
|
|
# Staff Involved
|
|
requested_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='requested_transfers',
|
|
help_text='Staff member who requested transfer'
|
|
)
|
|
approved_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='approved_transfers',
|
|
help_text='Staff member who approved transfer'
|
|
)
|
|
completed_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='completed_transfers',
|
|
help_text='Staff member who completed transfer'
|
|
)
|
|
|
|
# Transport Information
|
|
transport_method = models.CharField(
|
|
max_length=20,
|
|
choices=TRANSPORT_METHOD_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Method of transport'
|
|
)
|
|
transport_team = models.ManyToManyField(
|
|
settings.AUTH_USER_MODEL,
|
|
related_name='transport_assignments',
|
|
blank=True,
|
|
help_text='Transport team members'
|
|
)
|
|
|
|
# Equipment and Supplies
|
|
equipment_needed = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Equipment needed for transfer'
|
|
)
|
|
supplies_needed = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Supplies needed for transfer'
|
|
)
|
|
|
|
# Clinical Information
|
|
patient_condition = models.CharField(
|
|
max_length=20,
|
|
choices=PATIENT_CONDITION_CHOICES,
|
|
help_text='Patient condition at time of transfer'
|
|
)
|
|
vital_signs = models.JSONField(
|
|
default=dict,
|
|
blank=True,
|
|
help_text='Vital signs at transfer'
|
|
)
|
|
|
|
# Handoff Information
|
|
handoff_report = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Nursing handoff report'
|
|
)
|
|
medications_transferred = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Medications transferred with patient'
|
|
)
|
|
|
|
# Delays and Issues
|
|
delay_reason = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Reason for any delays'
|
|
)
|
|
complications = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Any complications during transfer'
|
|
)
|
|
|
|
# Notes
|
|
notes = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Additional transfer notes'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_transfer'
|
|
verbose_name = 'Transfer'
|
|
verbose_name_plural = 'Transfers'
|
|
ordering = ['-requested_datetime']
|
|
indexes = [
|
|
models.Index(fields=['admission', 'status']),
|
|
models.Index(fields=['patient']),
|
|
models.Index(fields=['from_ward', 'to_ward']),
|
|
models.Index(fields=['requested_datetime']),
|
|
models.Index(fields=['priority']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.patient.get_full_name()} - {self.from_ward.name} to {self.to_ward.name}"
|
|
|
|
@property
|
|
def transfer_duration(self):
|
|
"""
|
|
Calculate transfer duration if completed.
|
|
"""
|
|
if self.actual_datetime and self.requested_datetime:
|
|
return self.actual_datetime - self.requested_datetime
|
|
return None
|
|
|
|
|
|
class DischargeSummary(models.Model):
|
|
"""
|
|
Discharge summary model for documenting patient discharge.
|
|
"""
|
|
DISCHARGE_DISPOSITION_CHOICES = [
|
|
('HOME', 'Home'),
|
|
('HOME_HEALTH', 'Home with Health Services'),
|
|
('NURSING_HOME', 'Nursing Home'),
|
|
('REHAB_FACILITY', 'Rehabilitation Facility'),
|
|
('HOSPICE', 'Hospice'),
|
|
('TRANSFER', 'Transfer to Another Hospital'),
|
|
('MORGUE', 'Morgue'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
TRANSPORTATION_METHOD_CHOICES = [
|
|
('PRIVATE', 'Private Vehicle'),
|
|
('TAXI', 'Taxi'),
|
|
('AMBULANCE', 'Ambulance'),
|
|
('MEDICAL_TRANSPORT', 'Medical Transport'),
|
|
('PUBLIC_TRANSPORT', 'Public Transportation'),
|
|
('WALKING', 'Walking'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
PATIENT_UNDERSTANDING_CHOICES = [
|
|
('EXCELLENT', 'Excellent'),
|
|
('GOOD', 'Good'),
|
|
('FAIR', 'Fair'),
|
|
('POOR', 'Poor'),
|
|
]
|
|
|
|
READMISSION_RISK_CHOICES=[
|
|
('LOW', 'Low Risk'),
|
|
('MODERATE', 'Moderate Risk'),
|
|
('HIGH', 'High Risk'),
|
|
('VERY_HIGH', 'Very High Risk'),
|
|
]
|
|
# Admission relationship
|
|
admission = models.OneToOneField(
|
|
Admission,
|
|
on_delete=models.CASCADE,
|
|
related_name='discharge_summary',
|
|
help_text='Associated admission'
|
|
)
|
|
|
|
# Summary Information
|
|
summary_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique summary identifier'
|
|
)
|
|
|
|
# Discharge Details
|
|
discharge_date = models.DateField(
|
|
help_text='Discharge date'
|
|
)
|
|
discharge_time = models.TimeField(
|
|
help_text='Discharge time'
|
|
)
|
|
length_of_stay = models.PositiveIntegerField(
|
|
help_text='Total length of stay in days'
|
|
)
|
|
|
|
# Clinical Summary
|
|
admission_diagnosis = models.TextField(
|
|
help_text='Admission diagnosis'
|
|
)
|
|
final_diagnosis = models.TextField(
|
|
help_text='Final/discharge diagnosis'
|
|
)
|
|
secondary_diagnoses = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Secondary diagnoses'
|
|
)
|
|
procedures_performed = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Procedures performed during stay'
|
|
)
|
|
|
|
# Hospital Course
|
|
hospital_course = models.TextField(
|
|
help_text='Summary of hospital course'
|
|
)
|
|
complications = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Complications during stay'
|
|
)
|
|
|
|
# Discharge Medications
|
|
discharge_medications = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Medications at discharge'
|
|
)
|
|
medication_changes = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Changes to medications'
|
|
)
|
|
|
|
# Discharge Instructions
|
|
activity_restrictions = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Activity restrictions'
|
|
)
|
|
diet_instructions = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Diet instructions'
|
|
)
|
|
wound_care = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Wound care instructions'
|
|
)
|
|
special_instructions = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Special discharge instructions'
|
|
)
|
|
|
|
# Follow-up Care
|
|
follow_up_appointments = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Scheduled follow-up appointments'
|
|
)
|
|
follow_up_instructions = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Follow-up care instructions'
|
|
)
|
|
|
|
# Warning Signs
|
|
warning_signs = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Warning signs to watch for'
|
|
)
|
|
when_to_call = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='When to call healthcare provider'
|
|
)
|
|
|
|
# Discharge Disposition
|
|
discharge_disposition = models.CharField(
|
|
max_length=30,
|
|
choices=DISCHARGE_DISPOSITION_CHOICES,
|
|
help_text='Discharge disposition'
|
|
)
|
|
discharge_location = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Specific discharge location'
|
|
)
|
|
|
|
# Transportation
|
|
transportation_arranged = models.BooleanField(
|
|
default=False,
|
|
help_text='Transportation arranged for discharge'
|
|
)
|
|
transportation_method = models.CharField(
|
|
max_length=30,
|
|
choices=TRANSPORTATION_METHOD_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Method of transportation'
|
|
)
|
|
|
|
# Equipment and Supplies
|
|
durable_medical_equipment = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Durable medical equipment provided'
|
|
)
|
|
supplies_provided = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Medical supplies provided'
|
|
)
|
|
|
|
# Patient Education
|
|
education_provided = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Patient education topics covered'
|
|
)
|
|
education_materials = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Educational materials provided'
|
|
)
|
|
patient_understanding = models.CharField(
|
|
max_length=20,
|
|
choices=PATIENT_UNDERSTANDING_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Patient understanding of instructions'
|
|
)
|
|
|
|
# Discharge Planning
|
|
discharge_planner = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='planned_discharges',
|
|
help_text='Discharge planner'
|
|
)
|
|
social_worker_involved = models.BooleanField(
|
|
default=False,
|
|
help_text='Social worker involved in discharge planning'
|
|
)
|
|
case_manager_involved = models.BooleanField(
|
|
default=False,
|
|
help_text='Case manager involved in discharge planning'
|
|
)
|
|
|
|
# Quality Measures
|
|
readmission_risk = models.CharField(
|
|
max_length=20,
|
|
choices=READMISSION_RISK_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Risk of readmission'
|
|
)
|
|
patient_satisfaction = models.PositiveIntegerField(
|
|
blank=True,
|
|
null=True,
|
|
validators=[MinValueValidator(1), MaxValueValidator(10)],
|
|
help_text='Patient satisfaction score (1-10)'
|
|
)
|
|
|
|
# Provider Information
|
|
discharging_physician = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='physician_discharges',
|
|
help_text='Discharging physician'
|
|
)
|
|
primary_nurse = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='primary_nurse_discharges',
|
|
help_text='Primary nurse'
|
|
)
|
|
|
|
# Documentation
|
|
summary_completed = models.BooleanField(
|
|
default=False,
|
|
help_text='Discharge summary completed'
|
|
)
|
|
summary_signed = models.BooleanField(
|
|
default=False,
|
|
help_text='Discharge summary signed'
|
|
)
|
|
patient_copy_provided = models.BooleanField(
|
|
default=False,
|
|
help_text='Copy provided to patient'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_discharge_summaries',
|
|
help_text='User who created the summary'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_discharge_summary'
|
|
verbose_name = 'Discharge Summary'
|
|
verbose_name_plural = 'Discharge Summaries'
|
|
ordering = ['-discharge_date']
|
|
indexes = [
|
|
models.Index(fields=['admission']),
|
|
models.Index(fields=['discharge_date']),
|
|
models.Index(fields=['discharging_physician']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Discharge Summary - {self.admission.patient.get_full_name()} ({self.discharge_date})"
|
|
|
|
|
|
class SurgerySchedule(models.Model):
|
|
"""
|
|
Surgery schedule model for tracking surgical procedures.
|
|
"""
|
|
SURGERY_TYPE_CHOICES = [
|
|
('ELECTIVE', 'Elective'),
|
|
('URGENT', 'Urgent'),
|
|
('EMERGENT', 'Emergent'),
|
|
('TRAUMA', 'Trauma'),
|
|
('TRANSPLANT', 'Transplant'),
|
|
('CARDIAC', 'Cardiac'),
|
|
('NEUROSURGERY', 'Neurosurgery'),
|
|
('ORTHOPEDIC', 'Orthopedic'),
|
|
('GENERAL', 'General Surgery'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
ANESTHESIA_TYPE_CHOICES = [
|
|
('GENERAL', 'General Anesthesia'),
|
|
('REGIONAL', 'Regional Anesthesia'),
|
|
('LOCAL', 'Local Anesthesia'),
|
|
('SPINAL', 'Spinal Anesthesia'),
|
|
('EPIDURAL', 'Epidural Anesthesia'),
|
|
('MAC', 'Monitored Anesthesia Care'),
|
|
('SEDATION', 'Conscious Sedation'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
STATUS_CHOICES = [
|
|
('SCHEDULED', 'Scheduled'),
|
|
('CONFIRMED', 'Confirmed'),
|
|
('PREP', 'Pre-operative Prep'),
|
|
('IN_PROGRESS', 'In Progress'),
|
|
('COMPLETED', 'Completed'),
|
|
('CANCELLED', 'Cancelled'),
|
|
('POSTPONED', 'Postponed'),
|
|
('DELAYED', 'Delayed'),
|
|
]
|
|
RECOVERY_LOCATION_CHOICES = [
|
|
('PACU', 'Post-Anesthesia Care Unit'),
|
|
('ICU', 'Intensive Care Unit'),
|
|
('WARD', 'Regular Ward'),
|
|
('SAME_DAY', 'Same Day Surgery'),
|
|
('HOME', 'Home'),
|
|
('OTHER', 'Other'),
|
|
]
|
|
PRIORITY_CHOICES = [
|
|
('ROUTINE', 'Routine'),
|
|
('URGENT', 'Urgent'),
|
|
('EMERGENT', 'Emergent'),
|
|
('STAT', 'STAT'),
|
|
]
|
|
# Tenant relationship
|
|
tenant = models.ForeignKey(
|
|
'core.Tenant',
|
|
on_delete=models.CASCADE,
|
|
related_name='surgery_schedules',
|
|
help_text='Organization tenant'
|
|
)
|
|
|
|
# Surgery Information
|
|
surgery_id = models.UUIDField(
|
|
default=uuid.uuid4,
|
|
unique=True,
|
|
editable=False,
|
|
help_text='Unique surgery identifier'
|
|
)
|
|
|
|
# Patient and Admission
|
|
patient = models.ForeignKey(
|
|
'patients.PatientProfile',
|
|
on_delete=models.CASCADE,
|
|
related_name='surgeries',
|
|
help_text='Patient undergoing surgery'
|
|
)
|
|
admission = models.ForeignKey(
|
|
Admission,
|
|
on_delete=models.CASCADE,
|
|
related_name='surgeries',
|
|
help_text='Associated admission'
|
|
)
|
|
|
|
# Surgery Details
|
|
procedure_name = models.CharField(
|
|
max_length=200,
|
|
help_text='Name of surgical procedure'
|
|
)
|
|
procedure_code = models.CharField(
|
|
max_length=20,
|
|
blank=True,
|
|
null=True,
|
|
help_text='CPT or ICD procedure code'
|
|
)
|
|
surgery_type = models.CharField(
|
|
max_length=30,
|
|
choices=SURGERY_TYPE_CHOICES,
|
|
help_text='Type of surgery'
|
|
)
|
|
|
|
# Scheduling Information
|
|
scheduled_date = models.DateField(
|
|
help_text='Scheduled surgery date'
|
|
)
|
|
scheduled_start_time = models.TimeField(
|
|
help_text='Scheduled start time'
|
|
)
|
|
estimated_duration_minutes = models.PositiveIntegerField(
|
|
help_text='Estimated duration in minutes'
|
|
)
|
|
|
|
# Operating Room
|
|
operating_room = models.CharField(
|
|
max_length=20,
|
|
help_text='Operating room assignment'
|
|
)
|
|
or_block_time = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
null=True,
|
|
help_text='OR block time assignment'
|
|
)
|
|
|
|
# Surgical Team
|
|
primary_surgeon = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.CASCADE,
|
|
related_name='primary_surgeries',
|
|
help_text='Primary surgeon'
|
|
)
|
|
assistant_surgeons = models.ManyToManyField(
|
|
settings.AUTH_USER_MODEL,
|
|
related_name='assistant_surgeries',
|
|
blank=True,
|
|
help_text='Assistant surgeons'
|
|
)
|
|
anesthesiologist = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='inpatient_anesthesia_cases',
|
|
help_text='Anesthesiologist'
|
|
)
|
|
scrub_nurse = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='inpatient_scrub_cases',
|
|
help_text='Scrub nurse'
|
|
)
|
|
circulating_nurse = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='inpatient_circulating_cases',
|
|
help_text='Circulating nurse'
|
|
)
|
|
|
|
# Anesthesia Information
|
|
anesthesia_type = models.CharField(
|
|
max_length=30,
|
|
choices=ANESTHESIA_TYPE_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Type of anesthesia'
|
|
)
|
|
|
|
# Pre-operative Information
|
|
preop_diagnosis = models.TextField(
|
|
help_text='Pre-operative diagnosis'
|
|
)
|
|
preop_orders = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Pre-operative orders'
|
|
)
|
|
consent_obtained = models.BooleanField(
|
|
default=False,
|
|
help_text='Surgical consent obtained'
|
|
)
|
|
consent_date = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Date consent was obtained'
|
|
)
|
|
|
|
# Equipment and Supplies
|
|
special_equipment = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Special equipment needed'
|
|
)
|
|
implants_needed = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Implants or devices needed'
|
|
)
|
|
blood_products = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='Blood products needed'
|
|
)
|
|
|
|
# Surgery Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='SCHEDULED',
|
|
help_text='Surgery status'
|
|
)
|
|
|
|
# Actual Timing
|
|
actual_start_time = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual start time'
|
|
)
|
|
actual_end_time = models.DateTimeField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual end time'
|
|
)
|
|
actual_duration_minutes = models.PositiveIntegerField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual duration in minutes'
|
|
)
|
|
|
|
# Post-operative Information
|
|
postop_diagnosis = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Post-operative diagnosis'
|
|
)
|
|
procedure_performed = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Actual procedure performed'
|
|
)
|
|
complications = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Intraoperative complications'
|
|
)
|
|
|
|
# Recovery Information
|
|
recovery_location = models.CharField(
|
|
max_length=50,
|
|
choices=RECOVERY_LOCATION_CHOICES,
|
|
blank=True,
|
|
null=True,
|
|
help_text='Post-operative recovery location'
|
|
)
|
|
|
|
# Priority and Urgency
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=PRIORITY_CHOICES,
|
|
default='ROUTINE',
|
|
help_text='Surgery priority'
|
|
)
|
|
|
|
# Notes
|
|
surgery_notes = models.TextField(
|
|
blank=True,
|
|
null=True,
|
|
help_text='Surgery notes'
|
|
)
|
|
|
|
# Metadata
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='created_surgeries',
|
|
help_text='User who created the surgery schedule'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'inpatients_surgery_schedule'
|
|
verbose_name = 'Surgery Schedule'
|
|
verbose_name_plural = 'Surgery Schedules'
|
|
ordering = ['scheduled_date', 'scheduled_start_time']
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'status']),
|
|
models.Index(fields=['patient']),
|
|
models.Index(fields=['admission']),
|
|
models.Index(fields=['scheduled_date']),
|
|
models.Index(fields=['primary_surgeon']),
|
|
models.Index(fields=['operating_room']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.procedure_name} - {self.patient.get_full_name()} ({self.scheduled_date})"
|
|
|
|
@property
|
|
def is_today(self):
|
|
"""
|
|
Check if surgery is scheduled for today.
|
|
"""
|
|
return self.scheduled_date == timezone.now().date()
|
|
|
|
@property
|
|
def duration_variance(self):
|
|
"""
|
|
Calculate variance between estimated and actual duration.
|
|
"""
|
|
if self.actual_duration_minutes:
|
|
return self.actual_duration_minutes - self.estimated_duration_minutes
|
|
return None
|
|
|