2025-10-06 15:25:37 +03:00

1446 lines
41 KiB
Python

"""
Operating Theatre app models for hospital management system.
Provides surgical scheduling, OR management, and perioperative workflows.
"""
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 django.contrib.contenttypes.fields import GenericRelation
from datetime import timedelta, datetime, date, time
from decimal import Decimal
import json
class OperatingRoom(models.Model):
"""
Operating room model for OR configuration and management.
"""
class OperatingRoomType(models.TextChoices):
GENERAL = 'GENERAL', 'General Surgery'
CARDIAC = 'CARDIAC', 'Cardiac Surgery'
NEURO = 'NEURO', 'Neurosurgery'
ORTHOPEDIC = 'ORTHOPEDIC', 'Orthopedic Surgery'
TRAUMA = 'TRAUMA', 'Trauma Surgery'
PEDIATRIC = 'PEDIATRIC', 'Pediatric Surgery'
OBSTETRIC = 'OBSTETRIC', 'Obstetric Surgery'
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
ENT = 'ENT', 'ENT Surgery'
UROLOGY = 'UROLOGY', 'Urology'
PLASTIC = 'PLASTIC', 'Plastic Surgery'
VASCULAR = 'VASCULAR', 'Vascular Surgery'
THORACIC = 'THORACIC', 'Thoracic Surgery'
TRANSPLANT = 'TRANSPLANT', 'Transplant Surgery'
ROBOTIC = 'ROBOTIC', 'Robotic Surgery'
HYBRID = 'HYBRID', 'Hybrid OR'
AMBULATORY = 'AMBULATORY', 'Ambulatory Surgery'
EMERGENCY = 'EMERGENCY', 'Emergency Surgery'
class ORStatus(models.TextChoices):
AVAILABLE = 'AVAILABLE', 'Available'
OCCUPIED = 'OCCUPIED', 'Occupied'
CLEANING = 'CLEANING', 'Cleaning'
MAINTENANCE = 'MAINTENANCE', 'Maintenance'
SETUP = 'SETUP', 'Setup'
TURNOVER = 'TURNOVER', 'Turnover'
OUT_OF_ORDER = 'OUT_OF_ORDER', 'Out of Order'
CLOSED = 'CLOSED', 'Closed'
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='operating_rooms',
help_text='Organization tenant'
)
# Room Information
room_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique room identifier'
)
room_number = models.CharField(
max_length=20,
help_text='Operating room number'
)
room_name = models.CharField(
max_length=100,
help_text='Operating room name'
)
# Room Type and Capabilities
room_type = models.CharField(
max_length=30,
choices=OperatingRoomType.choices,
help_text='Operating room type'
)
# Room Status
status = models.CharField(
max_length=20,
choices=ORStatus.choices,
default=ORStatus.AVAILABLE,
help_text='Current room status'
)
# Physical Characteristics
floor_number = models.PositiveIntegerField(
help_text='Floor number'
)
room_size = models.FloatField(
blank=True,
null=True,
help_text='Room size in square meters'
)
ceiling_height = models.FloatField(
blank=True,
null=True,
help_text='Ceiling height in meters'
)
# Environmental Controls
temperature_min = models.FloatField(
default=18.0,
help_text='Minimum temperature in Celsius'
)
temperature_max = models.FloatField(
default=26.0,
help_text='Maximum temperature in Celsius'
)
humidity_min = models.FloatField(
default=30.0,
help_text='Minimum humidity percentage'
)
humidity_max = models.FloatField(
default=60.0,
help_text='Maximum humidity percentage'
)
air_changes_per_hour = models.PositiveIntegerField(
default=20,
help_text='Air changes per hour'
)
positive_pressure = models.BooleanField(
default=True,
help_text='Positive pressure room'
)
# Equipment and Features
equipment_list = models.JSONField(
default=list,
help_text='Available equipment list'
)
special_features = models.JSONField(
default=list,
help_text='Special features and capabilities'
)
# Imaging Capabilities
has_c_arm = models.BooleanField(
default=False,
help_text='C-arm fluoroscopy available'
)
has_ct = models.BooleanField(
default=False,
help_text='Intraoperative CT available'
)
has_mri = models.BooleanField(
default=False,
help_text='Intraoperative MRI available'
)
has_ultrasound = models.BooleanField(
default=False,
help_text='Ultrasound available'
)
has_neuromonitoring = models.BooleanField(
default=False,
help_text='Neuromonitoring available'
)
# Surgical Capabilities
supports_robotic = models.BooleanField(
default=False,
help_text='Robotic surgery capable'
)
supports_laparoscopic = models.BooleanField(
default=True,
help_text='Laparoscopic surgery capable'
)
supports_microscopy = models.BooleanField(
default=False,
help_text='Surgical microscopy available'
)
supports_laser = models.BooleanField(
default=False,
help_text='Laser surgery capable'
)
# Capacity and Scheduling
max_case_duration = models.PositiveIntegerField(
default=480,
help_text='Maximum case duration in minutes'
)
turnover_time = models.PositiveIntegerField(
default=30,
help_text='Standard turnover time in minutes'
)
cleaning_time = models.PositiveIntegerField(
default=45,
help_text='Deep cleaning time in minutes'
)
# Staffing Requirements
required_nurses = models.PositiveIntegerField(
default=2,
help_text='Required number of nurses'
)
required_techs = models.PositiveIntegerField(
default=1,
help_text='Required number of technicians'
)
# Availability
is_active = models.BooleanField(
default=True,
help_text='Room is active and available for scheduling'
)
accepts_emergency = models.BooleanField(
default=True,
help_text='Accepts emergency cases'
)
# Location Information
building = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Building name or identifier'
)
wing = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Wing or section'
)
# 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_operating_rooms',
help_text='User who created the room'
)
class Meta:
db_table = 'operating_theatre_operating_room'
verbose_name = 'Operating Room'
verbose_name_plural = 'Operating Rooms'
ordering = ['room_number']
indexes = [
models.Index(fields=['tenant', 'is_active']),
models.Index(fields=['room_type', 'status']),
models.Index(fields=['floor_number']),
models.Index(fields=['accepts_emergency']),
]
unique_together = ['tenant', 'room_number']
def __str__(self):
return f"OR {self.room_number} - {self.room_name}"
@property
def is_available(self):
"""
Check if room is available for scheduling.
"""
return self.status == 'AVAILABLE' and self.is_active
@property
def surgical_cases(self):
"""
All surgical cases scheduled/assigned to this operating room
via its OR blocks.
"""
return SurgicalCase.objects.filter(or_block__operating_room=self)
# (Optional) a clearer alias if you prefer not to shadow the term "surgical_cases"
@property
def cases(self):
return self.surgical_cases
@property
def current_case(self):
"""
Get the in-progress surgical case for this room (if any).
"""
return self.surgical_cases.filter(status='IN_PROGRESS').order_by('-scheduled_start').first()
class ORBlock(models.Model):
"""
OR block model for surgical scheduling and time management.
"""
class BlockType(models.TextChoices):
SCHEDULED = 'SCHEDULED', 'Scheduled Block'
EMERGENCY = 'EMERGENCY', 'Emergency Block'
MAINTENANCE = 'MAINTENANCE', 'Maintenance Block'
CLEANING = 'CLEANING', 'Deep Cleaning'
RESERVED = 'RESERVED', 'Reserved'
BLOCKED = 'BLOCKED', 'Blocked'
class ORService(models.TextChoices):
GENERAL = 'GENERAL', 'General Surgery'
CARDIAC = 'CARDIAC', 'Cardiac Surgery'
NEURO = 'NEURO', 'Neurosurgery'
ORTHOPEDIC = 'ORTHOPEDIC', 'Orthopedic Surgery'
TRAUMA = 'TRAUMA', 'Trauma Surgery'
PEDIATRIC = 'PEDIATRIC', 'Pediatric Surgery'
OBSTETRIC = 'OBSTETRIC', 'Obstetric Surgery'
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
ENT = 'ENT', 'ENT Surgery'
UROLOGY = 'UROLOGY', 'Urology'
PLASTIC = 'PLASTIC', 'Plastic Surgery'
VASCULAR = 'VASCULAR', 'Vascular Surgery'
THORACIC = 'THORACIC', 'Thoracic Surgery'
TRANSPLANT = 'TRANSPLANT', 'Transplant Surgery'
class BlockStatus(models.TextChoices):
SCHEDULED = 'SCHEDULED', 'Scheduled'
ACTIVE = 'ACTIVE', 'Active'
COMPLETED = 'COMPLETED', 'Completed'
CANCELLED = 'CANCELLED', 'Cancelled'
DELAYED = 'DELAYED', 'Delayed'
# Operating Room relationship
operating_room = models.ForeignKey(
OperatingRoom,
on_delete=models.CASCADE,
related_name='or_blocks',
help_text='Operating room'
)
# Block Information
block_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique block identifier'
)
# Block Timing
date = models.DateField(
help_text='Block date'
)
start_time = models.TimeField(
help_text='Block start time'
)
end_time = models.TimeField(
help_text='Block end time'
)
# Block Type
block_type = models.CharField(
max_length=20,
choices=BlockType.choices,
default=BlockType.SCHEDULED,
help_text='Block type'
)
# Surgeon Assignment
primary_surgeon = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='primary_or_blocks',
help_text='Primary surgeon assigned to block'
)
assistant_surgeons = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name='assistant_or_blocks',
help_text='Assistant surgeons'
)
# Service Assignment
service = models.CharField(
max_length=30,
choices=ORService.choices,
help_text='Surgical service'
)
# Block Status
status = models.CharField(
max_length=20,
choices=BlockStatus.choices,
default=BlockStatus.SCHEDULED,
help_text='Block status'
)
# Utilization
allocated_minutes = models.PositiveIntegerField(
help_text='Total allocated minutes'
)
used_minutes = models.PositiveIntegerField(
default=0,
help_text='Minutes actually used'
)
# Special Requirements
special_equipment = models.JSONField(
default=list,
help_text='Special equipment requirements'
)
special_setup = models.TextField(
blank=True,
null=True,
help_text='Special setup requirements'
)
# Notes
notes = models.TextField(
blank=True,
null=True,
help_text='Block notes and comments'
)
# 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_or_blocks',
help_text='User who created the block'
)
class Meta:
db_table = 'operating_theatre_or_block'
verbose_name = 'OR Block'
verbose_name_plural = 'OR Blocks'
ordering = ['date', 'start_time']
indexes = [
models.Index(fields=['operating_room', 'date']),
models.Index(fields=['primary_surgeon', 'date']),
models.Index(fields=['service', 'date']),
models.Index(fields=['status']),
]
def __str__(self):
return f"{self.operating_room.room_number} - {self.date} {self.start_time}-{self.end_time}"
def save(self, *args, **kwargs):
"""
Calculate allocated minutes from start and end times.
"""
if self.start_time and self.end_time:
start_datetime = datetime.combine(date.today(), self.start_time)
end_datetime = datetime.combine(date.today(), self.end_time)
if end_datetime < start_datetime:
end_datetime += timedelta(days=1)
delta = end_datetime - start_datetime
self.allocated_minutes = int(delta.total_seconds() / 60)
super().save(*args, **kwargs)
@property
def utilization_percentage(self):
"""
Calculate block utilization percentage.
"""
if self.allocated_minutes > 0:
return round((self.used_minutes / self.allocated_minutes) * 100, 1)
return 0
@property
def tenant(self):
"""
Get tenant from operating room.
"""
return self.operating_room.tenant
class SurgicalCase(models.Model):
"""
Surgical case model for individual surgical procedures.
"""
class CaseType(models.TextChoices):
ELECTIVE = 'ELECTIVE', 'Elective'
URGENT = 'URGENT', 'Urgent'
EMERGENCY = 'EMERGENCY', 'Emergency'
TRAUMA = 'TRAUMA', 'Trauma'
TRANSPLANT = 'TRANSPLANT', 'Transplant'
class SurgicalApproach(models.TextChoices):
OPEN = 'OPEN', 'Open'
LAPAROSCOPIC = 'LAPAROSCOPIC', 'Laparoscopic'
ROBOTIC = 'ROBOTIC', 'Robotic'
ENDOSCOPIC = 'ENDOSCOPIC', 'Endoscopic'
PERCUTANEOUS = 'PERCUTANEOUS', 'Percutaneous'
HYBRID = 'HYBRID', 'Hybrid'
class AnesthesiaType(models.TextChoices):
GENERAL = 'GENERAL', 'General'
REGIONAL = 'REGIONAL', 'Regional'
LOCAL = 'LOCAL', 'Local'
SEDATION = 'SEDATION', 'Sedation'
SPINAL = 'SPINAL', 'Spinal'
EPIDURAL = 'EPIDURAL', 'Epidural'
COMBINED = 'COMBINED', 'Combined'
class CaseStatus(models.TextChoices):
SCHEDULED = 'SCHEDULED', 'Scheduled'
CONFIRMED = 'CONFIRMED', 'Confirmed'
PREP = 'PREP', 'Pre-operative Prep'
DELAYED = 'DELAYED', 'Delayed'
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
COMPLETED = 'COMPLETED', 'Completed'
CANCELLED = 'CANCELLED', 'Cancelled'
POSTPONED = 'POSTPONED', 'Postponed'
class PatientPosition(models.TextChoices):
SUPINE = 'SUPINE', 'Supine'
PRONE = 'PRONE', 'Prone'
LATERAL = 'LATERAL', 'Lateral'
LITHOTOMY = 'LITHOTOMY', 'Lithotomy'
TRENDELENBURG = 'TRENDELENBURG', 'Trendelenburg'
REVERSE_TREND = 'REVERSE_TREND', 'Reverse Trendelenburg'
SITTING = 'SITTING', 'Sitting'
JACKKNIFE = 'JACKKNIFE', 'Jackknife'
# OR Block relationship
or_block = models.ForeignKey(
ORBlock,
on_delete=models.CASCADE,
related_name='surgical_cases',
help_text='OR block assignment'
)
# Case Information
case_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique case identifier'
)
case_number = models.CharField(
max_length=20,
unique=True,
help_text='Surgical case number'
)
# Patient Information
patient = models.ForeignKey(
'patients.PatientProfile',
on_delete=models.CASCADE,
related_name='surgical_cases',
help_text='Patient'
)
# Surgical Team
primary_surgeon = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='primary_surgical_cases',
help_text='Primary surgeon'
)
assistant_surgeons = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name='assistant_surgical_cases',
help_text='Assistant surgeons'
)
anesthesiologist = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='anesthesia_cases',
help_text='Anesthesiologist'
)
circulating_nurse = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='circulating_cases',
help_text='Circulating nurse'
)
scrub_nurse = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='scrub_cases',
help_text='Scrub nurse'
)
# Procedure Information
primary_procedure = models.CharField(
max_length=200,
help_text='Primary surgical procedure'
)
secondary_procedures = models.JSONField(
default=list,
help_text='Secondary procedures'
)
procedure_codes = models.JSONField(
default=list,
help_text='CPT procedure codes'
)
# Case Classification
case_type = models.CharField(
max_length=20,
choices=CaseType.choices,
default=CaseType.ELECTIVE,
help_text='Case type'
)
# Surgical Approach
approach = models.CharField(
max_length=20,
choices=SurgicalApproach.choices,
help_text='Surgical approach'
)
# Anesthesia
anesthesia_type = models.CharField(
max_length=20,
choices=AnesthesiaType.choices,
help_text='Anesthesia type'
)
# Timing
scheduled_start = models.DateTimeField(
help_text='Scheduled start time'
)
estimated_duration = models.PositiveIntegerField(
help_text='Estimated duration in minutes'
)
actual_start = models.DateTimeField(
blank=True,
null=True,
help_text='Actual start time'
)
actual_end = models.DateTimeField(
blank=True,
null=True,
help_text='Actual end time'
)
# Case Status
status = models.CharField(
max_length=20,
choices=CaseStatus.choices,
default=CaseStatus.SCHEDULED,
help_text='Case status'
)
# Clinical Information
diagnosis = models.CharField(
max_length=200,
help_text='Primary diagnosis'
)
diagnosis_codes = models.JSONField(
default=list,
help_text='ICD-10 diagnosis codes'
)
clinical_notes = models.TextField(
blank=True,
null=True,
help_text='Clinical notes and history'
)
# Special Requirements
special_equipment = models.JSONField(
default=list,
help_text='Special equipment requirements'
)
blood_products = models.JSONField(
default=list,
help_text='Blood product requirements'
)
implants = models.JSONField(
default=list,
help_text='Implant requirements'
)
# Patient Positioning
patient_position = models.CharField(
max_length=20,
choices=PatientPosition.choices,
blank=True,
null=True,
help_text='Patient positioning'
)
# Complications and Outcomes
complications = models.JSONField(
default=list,
help_text='Intraoperative complications'
)
estimated_blood_loss = models.PositiveIntegerField(
blank=True,
null=True,
help_text='Estimated blood loss in mL'
)
# Related Information
encounter = models.ForeignKey(
'emr.Encounter',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='surgical_cases',
help_text='Related encounter'
)
admission = models.ForeignKey(
'inpatients.Admission',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='surgical_cases',
help_text='Related admission'
)
# Insurance Approval Integration
approval_requests = GenericRelation(
'insurance_approvals.InsuranceApprovalRequest',
content_type_field='content_type',
object_id_field='object_id',
related_query_name='surgical_case'
)
# 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_surgical_cases',
help_text='User who created the case'
)
class Meta:
db_table = 'operating_theatre_surgical_case'
verbose_name = 'Surgical Case'
verbose_name_plural = 'Surgical Cases'
ordering = ['scheduled_start']
indexes = [
models.Index(fields=['or_block', 'status']),
models.Index(fields=['patient', 'scheduled_start']),
models.Index(fields=['primary_surgeon', 'scheduled_start']),
models.Index(fields=['case_type', 'status']),
models.Index(fields=['scheduled_start']),
models.Index(fields=['case_number']),
]
def __str__(self):
return f"{self.case_number} - {self.primary_procedure}"
def save(self, *args, **kwargs):
"""
Generate case number if not provided.
"""
if not self.case_number:
# Generate case number (simple implementation)
tenant = self.or_block.operating_room.tenant
today = timezone.now().date()
last_case = SurgicalCase.objects.filter(
or_block__operating_room__tenant=tenant,
created_at__date=today
).order_by('-id').first()
if last_case:
last_number = int(last_case.case_number.split('-')[-1])
self.case_number = f"SURG-{today.strftime('%Y%m%d')}-{last_number + 1:04d}"
else:
self.case_number = f"SURG-{today.strftime('%Y%m%d')}-0001"
super().save(*args, **kwargs)
@property
def actual_duration(self):
"""
Calculate actual case duration in minutes.
"""
if self.actual_start and self.actual_end:
delta = self.actual_end - self.actual_start
return int(delta.total_seconds() / 60)
return None
@property
def is_emergency(self):
"""
Check if case is emergency.
"""
return self.case_type in ['EMERGENCY', 'TRAUMA']
@property
def operating_room(self):
"""
Get operating room from OR block.
"""
return self.or_block.operating_room
@property
def tenant(self):
"""
Get tenant from OR block.
"""
return self.or_block.operating_room.tenant
def has_valid_approval(self):
"""
Check if surgical case has a valid insurance approval.
"""
from django.utils import timezone
return self.approval_requests.filter(
status__in=['APPROVED', 'PARTIALLY_APPROVED'],
expiration_date__gte=timezone.now().date()
).exists()
def get_active_approval(self):
"""
Get the active insurance approval for this surgical case.
"""
from django.utils import timezone
return self.approval_requests.filter(
status__in=['APPROVED', 'PARTIALLY_APPROVED'],
expiration_date__gte=timezone.now().date()
).first()
def requires_approval(self):
"""
Check if surgical case requires insurance approval.
Returns True if patient has insurance and no valid approval exists.
Emergency cases may have different requirements.
"""
if not self.patient.insurance_info.exists():
return False
# Emergency cases might have expedited approval process
if self.is_emergency:
# Check if emergency approval exists
return not self.has_valid_approval()
# Elective cases require approval
return not self.has_valid_approval()
@property
def approval_status(self):
"""
Get current approval status for display.
"""
if not self.patient.insurance_info.exists():
return 'NO_INSURANCE'
latest_approval = self.approval_requests.order_by('-created_at').first()
if not latest_approval:
if self.is_emergency:
return 'EMERGENCY_APPROVAL_REQUIRED'
return 'APPROVAL_REQUIRED'
if self.has_valid_approval():
return 'APPROVED'
return latest_approval.status
class SurgicalNote(models.Model):
"""
Surgical note model for perioperative documentation.
"""
class PatientCondition(models.TextChoices):
STABLE = 'STABLE', 'Stable'
CRITICAL = 'CRITICAL', 'Critical'
GUARDED = 'GUARDED', 'Guarded'
FAIR = 'FAIR', 'Fair'
GOOD = 'GOOD', 'Good'
EXCELLENT = 'EXCELLENT', 'Excellent'
class Disposition(models.TextChoices):
RECOVERY = 'RECOVERY', 'Recovery Room'
ICU = 'ICU', 'Intensive Care Unit'
WARD = 'WARD', 'Ward'
DISCHARGE = 'DISCHARGE', 'Discharge'
MORGUE = 'MORGUE', 'Morgue'
class NoteStatus(models.TextChoices):
DRAFT = 'DRAFT', 'Draft'
COMPLETED = 'COMPLETED', 'Completed'
SIGNED = 'SIGNED', 'Signed'
AMENDED = 'AMENDED', 'Amended'
# Surgical Case relationship
surgical_case = models.OneToOneField(
SurgicalCase,
on_delete=models.CASCADE,
related_name='surgical_notes',
help_text='Related surgical case'
)
# Note Information
note_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique note identifier'
)
# Surgeon Information
surgeon = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='surgeon_surgical_notes',
help_text='Operating surgeon'
)
# Preoperative Information
preoperative_diagnosis = models.TextField(
help_text='Preoperative diagnosis'
)
planned_procedure = models.TextField(
help_text='Planned surgical procedure'
)
indication = models.TextField(
help_text='Indication for surgery'
)
# Intraoperative Information
procedure_performed = models.TextField(
help_text='Actual procedure performed'
)
surgical_approach = models.TextField(
help_text='Surgical approach and technique'
)
findings = models.TextField(
help_text='Intraoperative findings'
)
technique = models.TextField(
help_text='Detailed surgical technique'
)
# Postoperative Information
postoperative_diagnosis = models.TextField(
help_text='Postoperative diagnosis'
)
condition = models.CharField(
max_length=20,
choices=PatientCondition.choices,
help_text='Patient condition post-surgery'
)
disposition = models.CharField(
max_length=30,
choices=Disposition.choices,
help_text='Patient disposition'
)
# Complications and Blood Loss
complications = models.TextField(
blank=True,
null=True,
help_text='Intraoperative complications'
)
estimated_blood_loss = models.PositiveIntegerField(
blank=True,
null=True,
help_text='Estimated blood loss in mL'
)
blood_transfusion = models.TextField(
blank=True,
null=True,
help_text='Blood transfusion details'
)
# Specimens and Pathology
specimens = models.TextField(
blank=True,
null=True,
help_text='Specimens sent to pathology'
)
# Implants and Devices
implants = models.TextField(
blank=True,
null=True,
help_text='Implants and devices used'
)
# Drains and Tubes
drains = models.TextField(
blank=True,
null=True,
help_text='Drains and tubes placed'
)
# Closure
closure = models.TextField(
blank=True,
null=True,
help_text='Wound closure technique'
)
# Postoperative Instructions
postop_instructions = models.TextField(
blank=True,
null=True,
help_text='Postoperative instructions'
)
follow_up = models.TextField(
blank=True,
null=True,
help_text='Follow-up instructions'
)
# Note Status
status = models.CharField(
max_length=20,
choices=NoteStatus.choices,
default=NoteStatus.DRAFT,
help_text='Note status'
)
# Signatures
signed_datetime = models.DateTimeField(
blank=True,
null=True,
help_text='Date and time note was signed'
)
# Template
template_used = models.ForeignKey(
'SurgicalNoteTemplate',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='surgical_notes',
help_text='Template used for note'
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'operating_theatre_surgical_note'
verbose_name = 'Surgical Note'
verbose_name_plural = 'Surgical Notes'
ordering = ['-created_at']
indexes = [
models.Index(fields=['surgical_case']),
models.Index(fields=['surgeon']),
models.Index(fields=['status']),
models.Index(fields=['signed_datetime']),
]
def __str__(self):
return f"Surgical Note - {self.surgical_case.case_number}"
@property
def patient(self):
"""
Get patient from surgical case.
"""
return self.surgical_case.patient
@property
def is_signed(self):
"""
Check if note is signed.
"""
return self.status == 'SIGNED' and self.signed_datetime is not None
class EquipmentUsage(models.Model):
"""
Equipment usage model for tracking surgical equipment.
"""
class EquipmentType(models.TextChoices):
SURGICAL_INSTRUMENT = 'SURGICAL_INSTRUMENT', 'Surgical Instrument'
MONITORING_DEVICE = 'MONITORING_DEVICE', 'Monitoring Device'
ANESTHESIA_MACHINE = 'ANESTHESIA_MACHINE', 'Anesthesia Machine'
VENTILATOR = 'VENTILATOR', 'Ventilator'
ELECTROCAUTERY = 'ELECTROCAUTERY', 'Electrocautery'
LASER = 'LASER', 'Laser'
MICROSCOPE = 'MICROSCOPE', 'Microscope'
C_ARM = 'C_ARM', 'C-Arm'
ULTRASOUND = 'ULTRASOUND', 'Ultrasound'
ROBOT = 'ROBOT', 'Surgical Robot'
IMPLANT = 'IMPLANT', 'Implant'
DISPOSABLE = 'DISPOSABLE', 'Disposable'
OTHER = 'OTHER', 'Other'
class UnitOfMeasure(models.TextChoices): # rename if you already have a global UnitOfMeasure
EACH = 'EACH', 'Each'
SET = 'SET', 'Set'
PACK = 'PACK', 'Pack'
BOX = 'BOX', 'Box'
UNIT = 'UNIT', 'Unit'
PIECE = 'PIECE', 'Piece'
# Surgical Case relationship
surgical_case = models.ForeignKey(
SurgicalCase,
on_delete=models.CASCADE,
related_name='equipment_usage',
help_text='Related surgical case'
)
# Equipment Information
usage_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique usage identifier'
)
equipment_name = models.CharField(
max_length=100,
help_text='Equipment name'
)
equipment_type = models.CharField(
max_length=50,
choices=EquipmentType.choices,
help_text='Equipment type'
)
# Equipment Details
manufacturer = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Equipment manufacturer'
)
model = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Equipment model'
)
serial_number = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Equipment serial number'
)
# Usage Information
quantity_used = models.PositiveIntegerField(
default=1,
help_text='Quantity used'
)
unit_of_measure = models.CharField(
max_length=20,
choices=UnitOfMeasure.choices,
default='EACH',
help_text='Unit of measure'
)
# Timing
start_time = models.DateTimeField(
blank=True,
null=True,
help_text='Equipment usage start time'
)
end_time = models.DateTimeField(
blank=True,
null=True,
help_text='Equipment usage end time'
)
# Cost Information
unit_cost = models.DecimalField(
max_digits=10,
decimal_places=2,
blank=True,
null=True,
help_text='Unit cost'
)
total_cost = models.DecimalField(
max_digits=10,
decimal_places=2,
blank=True,
null=True,
help_text='Total cost'
)
# Quality and Safety
lot_number = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Lot or batch number'
)
expiration_date = models.DateField(
blank=True,
null=True,
help_text='Expiration date'
)
sterilization_date = models.DateField(
blank=True,
null=True,
help_text='Sterilization date'
)
# Usage Notes
notes = models.TextField(
blank=True,
null=True,
help_text='Usage notes and comments'
)
# Staff Information
recorded_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='recorded_equipment_usage',
help_text='Staff member who recorded usage'
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'operating_theatre_equipment_usage'
verbose_name = 'Equipment Usage'
verbose_name_plural = 'Equipment Usage'
ordering = ['-created_at']
indexes = [
models.Index(fields=['surgical_case']),
models.Index(fields=['equipment_type']),
models.Index(fields=['equipment_name']),
models.Index(fields=['recorded_by']),
]
def __str__(self):
return f"{self.equipment_name} - {self.surgical_case.case_number}"
def save(self, *args, **kwargs):
"""
Calculate total cost from unit cost and quantity.
"""
if self.unit_cost and self.quantity_used:
self.total_cost = self.unit_cost * self.quantity_used
super().save(*args, **kwargs)
@property
def duration_minutes(self):
"""
Calculate usage duration in minutes.
"""
if self.start_time and self.end_time:
delta = self.end_time - self.start_time
return int(delta.total_seconds() / 60)
return None
@property
def patient(self):
"""
Get patient from surgical case.
"""
return self.surgical_case.patient
class SurgicalNoteTemplate(models.Model):
"""
Surgical note template model for standardized documentation.
"""
class SurgicalSpecialty(models.TextChoices):
ALL = 'ALL', 'All Specialties' # consider keeping this for UI filters only
GENERAL = 'GENERAL', 'General Surgery'
CARDIAC = 'CARDIAC', 'Cardiac Surgery'
NEURO = 'NEURO', 'Neurosurgery'
ORTHOPEDIC = 'ORTHOPEDIC', 'Orthopedic Surgery'
TRAUMA = 'TRAUMA', 'Trauma Surgery'
PEDIATRIC = 'PEDIATRIC', 'Pediatric Surgery'
OBSTETRIC = 'OBSTETRIC', 'Obstetric Surgery'
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
ENT = 'ENT', 'ENT Surgery'
UROLOGY = 'UROLOGY', 'Urology'
PLASTIC = 'PLASTIC', 'Plastic Surgery'
VASCULAR = 'VASCULAR', 'Vascular Surgery'
THORACIC = 'THORACIC', 'Thoracic Surgery'
TRANSPLANT = 'TRANSPLANT', 'Transplant Surgery'
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
on_delete=models.CASCADE,
related_name='surgical_note_templates',
help_text='Organization tenant'
)
# Template Information
template_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
editable=False,
help_text='Unique template identifier'
)
name = models.CharField(
max_length=100,
help_text='Template name'
)
description = models.TextField(
blank=True,
null=True,
help_text='Template description'
)
# Template Scope
procedure_type = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Applicable procedure type'
)
specialty = models.CharField(
max_length=30,
choices=SurgicalSpecialty.choices,
default=SurgicalSpecialty.ALL,
help_text='Applicable specialty'
)
# Template Content
preoperative_diagnosis_template = models.TextField(
blank=True,
null=True,
help_text='Preoperative diagnosis template'
)
planned_procedure_template = models.TextField(
blank=True,
null=True,
help_text='Planned procedure template'
)
indication_template = models.TextField(
blank=True,
null=True,
help_text='Indication template'
)
procedure_performed_template = models.TextField(
blank=True,
null=True,
help_text='Procedure performed template'
)
surgical_approach_template = models.TextField(
blank=True,
null=True,
help_text='Surgical approach template'
)
findings_template = models.TextField(
blank=True,
null=True,
help_text='Findings template'
)
technique_template = models.TextField(
blank=True,
null=True,
help_text='Technique template'
)
postoperative_diagnosis_template = models.TextField(
blank=True,
null=True,
help_text='Postoperative diagnosis template'
)
complications_template = models.TextField(
blank=True,
null=True,
help_text='Complications template'
)
specimens_template = models.TextField(
blank=True,
null=True,
help_text='Specimens template'
)
implants_template = models.TextField(
blank=True,
null=True,
help_text='Implants template'
)
closure_template = models.TextField(
blank=True,
null=True,
help_text='Closure template'
)
postop_instructions_template = models.TextField(
blank=True,
null=True,
help_text='Postoperative instructions template'
)
# Template Status
is_active = models.BooleanField(
default=True,
help_text='Template is active'
)
is_default = models.BooleanField(
default=False,
help_text='Default template for specialty'
)
# Usage Statistics
usage_count = models.PositiveIntegerField(
default=0,
help_text='Number of times template has been used'
)
# 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_surgical_note_templates',
help_text='User who created the template'
)
class Meta:
db_table = 'operating_theatre_surgical_note_template'
verbose_name = 'Surgical Note Template'
verbose_name_plural = 'Surgical Note Templates'
ordering = ['specialty', 'name']
indexes = [
models.Index(fields=['tenant', 'is_active']),
models.Index(fields=['specialty']),
models.Index(fields=['is_default']),
]
unique_together = ['tenant', 'name']
def __str__(self):
return f"{self.name} ({self.specialty})"
def save(self, *args, **kwargs):
"""
Ensure only one default template per specialty.
"""
if self.is_default:
# Remove default flag from other templates
SurgicalNoteTemplate.objects.filter(
tenant=self.tenant,
specialty=self.specialty,
is_default=True
).exclude(pk=self.pk).update(is_default=False)
super().save(*args, **kwargs)