1423 lines
41 KiB
Python
1423 lines
41 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.
|
|
"""
|
|
|
|
class WardType(models.TextChoices):
|
|
GENERAL = 'GENERAL', 'General Medical'
|
|
SURGICAL = 'SURGICAL', 'Surgical'
|
|
ICU = 'ICU', 'Intensive Care Unit'
|
|
CCU = 'CCU', 'Cardiac Care Unit'
|
|
NICU = 'NICU', 'Neonatal ICU'
|
|
PICU = 'PICU', 'Pediatric ICU'
|
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
|
MATERNITY = 'MATERNITY', 'Maternity'
|
|
PEDIATRIC = 'PEDIATRIC', 'Pediatric'
|
|
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
|
CARDIAC = 'CARDIAC', 'Cardiac'
|
|
ORTHOPEDIC = 'ORTHOPEDIC', 'Orthopedic'
|
|
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
|
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
|
REHABILITATION = 'REHABILITATION', 'Rehabilitation'
|
|
ISOLATION = 'ISOLATION', 'Isolation'
|
|
STEP_DOWN = 'STEP_DOWN', 'Step Down'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class WardSpecialty(models.TextChoices): # use WardSpeciality if you prefer UK spelling
|
|
GENERAL_MEDICINE = 'GENERAL_MEDICINE', 'General Medicine'
|
|
SURGERY = 'SURGERY', 'Surgery'
|
|
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
|
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
|
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
|
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
|
OBSTETRICS = 'OBSTETRICS', 'Obstetrics'
|
|
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
|
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
|
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
|
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
|
CRITICAL_CARE = 'CRITICAL_CARE', 'Critical Care'
|
|
REHABILITATION = 'REHABILITATION', 'Rehabilitation'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class GenderRestriction(models.TextChoices):
|
|
NONE = 'NONE', 'No Restrictions'
|
|
MALE_ONLY = 'MALE_ONLY', 'Male Only'
|
|
FEMALE_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=WardType.choices,
|
|
help_text='Type of ward'
|
|
)
|
|
|
|
specialty = models.CharField(
|
|
max_length=100,
|
|
choices=WardSpecialty.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.ForeignKey('facility_management.Building', on_delete=models.CASCADE, related_name='wards')
|
|
floor = models.ForeignKey('facility_management.Floor', on_delete=models.CASCADE, related_name='wards_floor')
|
|
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=GenderRestriction.choices,
|
|
default=GenderRestriction.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.
|
|
"""
|
|
|
|
class BedType(models.TextChoices):
|
|
STANDARD = 'STANDARD', 'Standard Bed'
|
|
ICU = 'ICU', 'ICU Bed'
|
|
CARDIAC = 'CARDIAC', 'Cardiac Monitoring'
|
|
ISOLATION = 'ISOLATION', 'Isolation Bed'
|
|
BARIATRIC = 'BARIATRIC', 'Bariatric Bed'
|
|
PEDIATRIC = 'PEDIATRIC', 'Pediatric Bed'
|
|
NEONATAL = 'NEONATAL', 'Neonatal Bed'
|
|
MATERNITY = 'MATERNITY', 'Maternity Bed'
|
|
PSYCHIATRIC = 'PSYCHIATRIC', 'Psychiatric Bed'
|
|
STRETCHER = 'STRETCHER', 'Stretcher/Gurney'
|
|
RECLINER = 'RECLINER', 'Recliner Chair'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class RoomType(models.TextChoices):
|
|
PRIVATE = 'PRIVATE', 'Private Room'
|
|
SEMI_PRIVATE = 'SEMI_PRIVATE', 'Semi-Private'
|
|
SHARED = 'SHARED', 'Shared Room'
|
|
ICU = 'ICU', 'ICU Room'
|
|
ISOLATION = 'ISOLATION', 'Isolation Room'
|
|
|
|
class BedStatus(models.TextChoices):
|
|
AVAILABLE = 'AVAILABLE', 'Available'
|
|
OCCUPIED = 'OCCUPIED', 'Occupied'
|
|
RESERVED = 'RESERVED', 'Reserved'
|
|
MAINTENANCE = 'MAINTENANCE', 'Under Maintenance'
|
|
CLEANING = 'CLEANING', 'Being Cleaned'
|
|
OUT_OF_ORDER = 'OUT_OF_ORDER', 'Out of Order'
|
|
BLOCKED = 'BLOCKED', 'Blocked'
|
|
|
|
class CleaningLevel(models.TextChoices):
|
|
STANDARD = 'STANDARD', 'Standard Cleaning'
|
|
DEEP = 'DEEP', 'Deep Cleaning'
|
|
ISOLATION = 'ISOLATION', 'Isolation Cleaning'
|
|
TERMINAL = 'TERMINAL', 'Terminal Cleaning'
|
|
|
|
class BedPosition(models.TextChoices):
|
|
A = 'A', 'A'
|
|
B = 'B', 'B'
|
|
C = 'C', 'C'
|
|
D = 'D', 'D'
|
|
E = 'E', 'E'
|
|
F = '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 = models.CharField(
|
|
max_length=30,
|
|
choices=BedType.choices,
|
|
default=BedType.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_out_of_service = models.BooleanField(
|
|
default=False,
|
|
)
|
|
room_type = models.CharField(
|
|
max_length=20,
|
|
choices=RoomType.choices,
|
|
help_text='Type of room'
|
|
)
|
|
|
|
# Bed Status
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=BedStatus.choices,
|
|
default=BedStatus.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.ManyToManyField('facility_management.Asset', related_name='beds_assets', blank=True)
|
|
|
|
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=CleaningLevel.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=BedPosition.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_admission']),
|
|
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
|
|
|
|
def mark_maintenance(self, notes=None):
|
|
"""
|
|
Mark bed for maintenance.
|
|
"""
|
|
self.status = 'MAINTENANCE'
|
|
self.last_maintenance = timezone.now()
|
|
if notes:
|
|
self.maintenance_notes = notes
|
|
self.save()
|
|
|
|
def mark_cleaned(self, user, cleaning_level='STANDARD'):
|
|
"""
|
|
Mark bed as cleaned.
|
|
"""
|
|
self.status = 'AVAILABLE'
|
|
self.last_cleaned = timezone.now()
|
|
self.cleaned_by = user
|
|
self.cleaning_level = cleaning_level
|
|
self.save()
|
|
|
|
|
|
class Admission(models.Model):
|
|
"""
|
|
Patient admission model for tracking inpatient stays.
|
|
"""
|
|
|
|
class AdmissionType(models.TextChoices):
|
|
EMERGENCY = 'EMERGENCY', 'Emergency Admission'
|
|
ELECTIVE = 'ELECTIVE', 'Elective Admission'
|
|
URGENT = 'URGENT', 'Urgent Admission'
|
|
OBSERVATION = 'OBSERVATION', 'Observation'
|
|
DAY_SURGERY = 'DAY_SURGERY', 'Day Surgery'
|
|
TRANSFER = 'TRANSFER', 'Transfer from Another Facility'
|
|
READMISSION = 'READMISSION', 'Readmission'
|
|
DIRECT = 'DIRECT', 'Direct Admission'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class AdmissionSource(models.TextChoices):
|
|
EMERGENCY = 'EMERGENCY', 'Emergency Department'
|
|
OUTPATIENT = 'OUTPATIENT', 'Outpatient Clinic'
|
|
PHYSICIAN_OFFICE = 'PHYSICIAN_OFFICE', 'Physician Office'
|
|
TRANSFER = 'TRANSFER', 'Transfer from Another Hospital'
|
|
NURSING_HOME = 'NURSING_HOME', 'Nursing Home'
|
|
HOME = 'HOME', 'Home'
|
|
AMBULATORY_SURGERY = 'AMBULATORY_SURGERY', 'Ambulatory Surgery'
|
|
OTHER_FACILITY = 'OTHER_FACILITY', 'Other Healthcare Facility'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class AdmissionStatus(models.TextChoices):
|
|
PENDING = 'PENDING', 'Pending Admission'
|
|
ADMITTED = 'ADMITTED', 'Admitted'
|
|
TRANSFERRED = 'TRANSFERRED', 'Transferred'
|
|
DISCHARGED = 'DISCHARGED', 'Discharged'
|
|
DECEASED = 'DECEASED', 'Deceased'
|
|
LEFT_AMA = 'LEFT_AMA', 'Left Against Medical Advice'
|
|
CANCELLED = 'CANCELLED', 'Cancelled'
|
|
|
|
class AdmissionPriority(models.TextChoices):
|
|
ROUTINE = 'ROUTINE', 'Routine'
|
|
URGENT = 'URGENT', 'Urgent'
|
|
EMERGENT = 'EMERGENT', 'Emergent'
|
|
CRITICAL = 'CRITICAL', 'Critical'
|
|
|
|
class DischargeDisposition(models.TextChoices):
|
|
HOME = 'HOME', 'Home'
|
|
HOME_HEALTH = 'HOME_HEALTH', 'Home with Health Services'
|
|
NURSING_HOME = 'NURSING_HOME', 'Nursing Home'
|
|
REHAB_FACILITY = 'REHAB_FACILITY', 'Rehabilitation Facility'
|
|
HOSPICE = 'HOSPICE', 'Hospice'
|
|
TRANSFER = 'TRANSFER', 'Transfer to Another Hospital'
|
|
DECEASED = 'DECEASED', 'Deceased'
|
|
LEFT_AMA = 'LEFT_AMA', 'Left Against Medical Advice'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class IsolationType(models.TextChoices):
|
|
CONTACT = 'CONTACT', 'Contact Precautions'
|
|
DROPLET = 'DROPLET', 'Droplet Precautions'
|
|
AIRBORNE = 'AIRBORNE', 'Airborne Precautions'
|
|
PROTECTIVE = 'PROTECTIVE', 'Protective Isolation'
|
|
STRICT = 'STRICT', 'Strict Isolation'
|
|
|
|
class CodeStatus(models.TextChoices):
|
|
FULL_CODE = 'FULL_CODE', 'Full Code'
|
|
DNR = 'DNR', 'Do Not Resuscitate'
|
|
DNI = 'DNI', 'Do Not Intubate'
|
|
DNR_DNI = 'DNR_DNI', 'DNR/DNI'
|
|
COMFORT_CARE = 'COMFORT_CARE', 'Comfort Care Only'
|
|
LIMITED = '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=AdmissionType.choices,
|
|
help_text='Type of admission'
|
|
)
|
|
|
|
admission_source = models.CharField(
|
|
max_length=30,
|
|
choices=AdmissionSource.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=AdmissionStatus.choices,
|
|
default=AdmissionStatus.PENDING,
|
|
help_text='Current admission status'
|
|
)
|
|
|
|
# Priority and Acuity
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=AdmissionPriority.choices,
|
|
default=AdmissionPriority.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=DischargeDisposition.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=IsolationType.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=CodeStatus.choices,
|
|
default=CodeStatus.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 DischargeSummary(models.Model):
|
|
"""
|
|
Discharge summary model for documenting patient discharge.
|
|
"""
|
|
|
|
class DischargeDisposition(models.TextChoices):
|
|
HOME = 'HOME', 'Home'
|
|
HOME_HEALTH = 'HOME_HEALTH', 'Home with Health Services'
|
|
NURSING_HOME = 'NURSING_HOME', 'Nursing Home'
|
|
REHAB_FACILITY = 'REHAB_FACILITY', 'Rehabilitation Facility'
|
|
HOSPICE = 'HOSPICE', 'Hospice'
|
|
TRANSFER = 'TRANSFER', 'Transfer to Another Hospital'
|
|
MORGUE = 'MORGUE', 'Morgue'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class TransportationMethod(models.TextChoices):
|
|
PRIVATE = 'PRIVATE', 'Private Vehicle'
|
|
TAXI = 'TAXI', 'Taxi'
|
|
AMBULANCE = 'AMBULANCE', 'Ambulance'
|
|
MEDICAL_TRANSPORT = 'MEDICAL_TRANSPORT', 'Medical Transport'
|
|
PUBLIC_TRANSPORT = 'PUBLIC_TRANSPORT', 'Public Transportation'
|
|
WALKING = 'WALKING', 'Walking'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
class PatientUnderstanding(models.TextChoices):
|
|
EXCELLENT = 'EXCELLENT', 'Excellent'
|
|
GOOD = 'GOOD', 'Good'
|
|
FAIR = 'FAIR', 'Fair'
|
|
POOR = 'POOR', 'Poor'
|
|
|
|
class ReadmissionRisk(models.TextChoices):
|
|
LOW = 'LOW', 'Low Risk'
|
|
MODERATE = 'MODERATE', 'Moderate Risk'
|
|
HIGH = 'HIGH', 'High Risk'
|
|
VERY_HIGH = '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=DischargeDisposition.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=TransportationMethod.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=PatientUnderstanding.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=ReadmissionRisk.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 Transfer(models.Model):
|
|
"""
|
|
Patient transfer model for tracking ward/bed changes.
|
|
"""
|
|
|
|
class TransferType(models.TextChoices):
|
|
WARD = 'WARD', 'Ward Transfer'
|
|
BED = 'BED', 'Bed Transfer'
|
|
ROOM = 'ROOM', 'Room Transfer'
|
|
UNIT = 'UNIT', 'Unit Transfer'
|
|
FACILITY = 'FACILITY', 'Facility Transfer'
|
|
|
|
class TransferStatus(models.TextChoices):
|
|
REQUESTED = 'REQUESTED', 'Requested'
|
|
APPROVED = 'APPROVED', 'Approved'
|
|
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
|
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
|
COMPLETED = 'COMPLETED', 'Completed'
|
|
CANCELLED = 'CANCELLED', 'Cancelled'
|
|
DELAYED = 'DELAYED', 'Delayed'
|
|
REJECTED = 'REJECTED', 'Rejected'
|
|
|
|
class TransferPriority(models.TextChoices):
|
|
ROUTINE = 'ROUTINE', 'Routine'
|
|
URGENT = 'URGENT', 'Urgent'
|
|
EMERGENT = 'EMERGENT', 'Emergent'
|
|
STAT = 'STAT', 'STAT'
|
|
|
|
class TransportMethod(models.TextChoices):
|
|
WHEELCHAIR = 'WHEELCHAIR', 'Wheelchair'
|
|
STRETCHER = 'STRETCHER', 'Stretcher'
|
|
BED = 'BED', 'Hospital Bed'
|
|
AMBULATORY = 'AMBULATORY', 'Walking'
|
|
AMBULANCE = 'AMBULANCE', 'Ambulance'
|
|
OTHER = 'OTHER', 'Other'
|
|
|
|
# Named to avoid collision with any general PatientCondition enum you may already have
|
|
class TransferPatientCondition(models.TextChoices):
|
|
STABLE = 'STABLE', 'Stable'
|
|
UNSTABLE = 'UNSTABLE', 'Unstable'
|
|
CRITICAL = 'CRITICAL', 'Critical'
|
|
IMPROVING = 'IMPROVING', 'Improving'
|
|
DETERIORATING = '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=TransferType.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=TransferStatus.choices,
|
|
default=TransferStatus.REQUESTED,
|
|
help_text='Transfer status'
|
|
)
|
|
|
|
# Transfer Reason and Priority
|
|
reason = models.TextField(
|
|
help_text='Reason for transfer'
|
|
)
|
|
priority = models.CharField(
|
|
max_length=20,
|
|
choices=TransferPriority.choices,
|
|
default=TransferPriority.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=TransportMethod.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=TransferPatientCondition.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
|