""" Collaboration & War Rooms models for Enterprise Incident Management API Implements real-time incident rooms, conference bridges, incident command roles, and timeline reconstruction """ import uuid import json from datetime import datetime, timedelta from typing import Dict, Any, Optional, List from django.db import models from django.contrib.auth import get_user_model from django.core.validators import MinValueValidator, MaxValueValidator from django.utils import timezone from django.core.exceptions import ValidationError User = get_user_model() class WarRoom(models.Model): """Real-time incident collaboration rooms (like Slack channels auto-created per incident)""" STATUS_CHOICES = [ ('ACTIVE', 'Active'), ('ARCHIVED', 'Archived'), ('CLOSED', 'Closed'), ] PRIVACY_CHOICES = [ ('PUBLIC', 'Public'), ('PRIVATE', 'Private'), ('RESTRICTED', 'Restricted'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=200) description = models.TextField(blank=True, null=True) # Related incident incident = models.ForeignKey( 'incident_intelligence.Incident', on_delete=models.CASCADE, related_name='war_rooms' ) # Room configuration status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE') privacy_level = models.CharField(max_length=20, choices=PRIVACY_CHOICES, default='PRIVATE') # Integration configuration slack_channel_id = models.CharField(max_length=100, blank=True, null=True, help_text="Slack channel ID") teams_channel_id = models.CharField(max_length=100, blank=True, null=True, help_text="Teams channel ID") discord_channel_id = models.CharField(max_length=100, blank=True, null=True, help_text="Discord channel ID") # Access control allowed_users = models.ManyToManyField( User, blank=True, related_name='accessible_war_rooms', help_text="Users with access to this war room" ) required_clearance_level = models.ForeignKey( 'security.DataClassification', on_delete=models.SET_NULL, null=True, blank=True, help_text="Required clearance level for access" ) # Activity tracking message_count = models.PositiveIntegerField(default=0) last_activity = models.DateTimeField(null=True, blank=True) active_participants = models.PositiveIntegerField(default=0) # Metadata created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='created_war_rooms') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) archived_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['incident', 'status']), models.Index(fields=['status', 'privacy_level']), models.Index(fields=['created_at']), ] def __str__(self): return f"War Room: {self.name} ({self.incident.title})" def can_user_access(self, user: User) -> bool: """Check if user can access this war room""" # Check clearance level requirement if self.required_clearance_level: if not user.has_data_access(self.required_clearance_level.level): return False # Check if user is explicitly allowed if self.allowed_users.exists(): return self.allowed_users.filter(id=user.id).exists() # Check incident access return self.incident.is_accessible_by_user(user) def add_participant(self, user: User): """Add user to war room participants""" if not self.allowed_users.filter(id=user.id).exists(): self.allowed_users.add(user) def remove_participant(self, user: User): """Remove user from war room participants""" self.allowed_users.remove(user) class ConferenceBridge(models.Model): """Conference bridge integration for incident collaboration""" BRIDGE_TYPES = [ ('ZOOM', 'Zoom'), ('TEAMS', 'Microsoft Teams'), ('WEBEX', 'Cisco Webex'), ('GOTO_MEETING', 'GoTo Meeting'), ('CUSTOM', 'Custom Bridge'), ] STATUS_CHOICES = [ ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('ENDED', 'Ended'), ('CANCELLED', 'Cancelled'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=200) description = models.TextField(blank=True, null=True) # Related incident and war room incident = models.ForeignKey( 'incident_intelligence.Incident', on_delete=models.CASCADE, related_name='conference_bridges' ) war_room = models.ForeignKey( WarRoom, on_delete=models.CASCADE, related_name='conference_bridges', null=True, blank=True ) # Bridge configuration bridge_type = models.CharField(max_length=20, choices=BRIDGE_TYPES) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='SCHEDULED') # Meeting details meeting_id = models.CharField(max_length=255, blank=True, null=True, help_text="External meeting ID") meeting_url = models.URLField(blank=True, null=True, help_text="Meeting URL") dial_in_number = models.CharField(max_length=50, blank=True, null=True, help_text="Dial-in phone number") access_code = models.CharField(max_length=20, blank=True, null=True, help_text="Access code for dial-in") # Schedule scheduled_start = models.DateTimeField(help_text="Scheduled start time") scheduled_end = models.DateTimeField(help_text="Scheduled end time") actual_start = models.DateTimeField(null=True, blank=True) actual_end = models.DateTimeField(null=True, blank=True) # Participants invited_participants = models.ManyToManyField( User, blank=True, related_name='invited_conferences', help_text="Users invited to the conference" ) active_participants = models.ManyToManyField( User, blank=True, related_name='active_conferences', help_text="Users currently in the conference" ) max_participants = models.PositiveIntegerField(default=50) # Recording and transcription recording_enabled = models.BooleanField(default=False) recording_url = models.URLField(blank=True, null=True, help_text="URL to recorded meeting") transcription_enabled = models.BooleanField(default=False) transcription_url = models.URLField(blank=True, null=True, help_text="URL to meeting transcription") # Integration configuration integration_config = models.JSONField( default=dict, help_text="Configuration for external bridge integration" ) # Metadata created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='created_conferences') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-scheduled_start'] indexes = [ models.Index(fields=['incident', 'status']), models.Index(fields=['bridge_type', 'status']), models.Index(fields=['scheduled_start']), ] def __str__(self): return f"Conference: {self.name} ({self.bridge_type})" def is_active(self) -> bool: """Check if conference is currently active""" now = timezone.now() return (self.status == 'ACTIVE' and self.scheduled_start <= now <= self.scheduled_end) def can_user_join(self, user: User) -> bool: """Check if user can join the conference""" # Check if user is invited if self.invited_participants.exists(): return self.invited_participants.filter(id=user.id).exists() # Check incident access return self.incident.is_accessible_by_user(user) def add_participant(self, user: User): """Add user to active participants""" if self.can_user_join(user): self.active_participants.add(user) if not self.invited_participants.filter(id=user.id).exists(): self.invited_participants.add(user) class IncidentCommandRole(models.Model): """Incident command roles and assignments""" ROLE_TYPES = [ ('INCIDENT_COMMANDER', 'Incident Commander'), ('SCRIBE', 'Scribe'), ('COMMS_LEAD', 'Communications Lead'), ('TECHNICAL_LEAD', 'Technical Lead'), ('BUSINESS_LEAD', 'Business Lead'), ('EXTERNAL_LIAISON', 'External Liaison'), ('OBSERVER', 'Observer'), ] STATUS_CHOICES = [ ('ACTIVE', 'Active'), ('INACTIVE', 'Inactive'), ('REASSIGNED', 'Reassigned'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Related incident and war room incident = models.ForeignKey( 'incident_intelligence.Incident', on_delete=models.CASCADE, related_name='command_roles' ) war_room = models.ForeignKey( WarRoom, on_delete=models.CASCADE, related_name='command_roles', null=True, blank=True ) # Role assignment role_type = models.CharField(max_length=30, choices=ROLE_TYPES) assigned_user = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_command_roles' ) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='ACTIVE') # Role responsibilities responsibilities = models.JSONField( default=list, help_text="List of responsibilities for this role" ) decision_authority = models.JSONField( default=list, help_text="Areas where this role has decision authority" ) # Assignment tracking assigned_at = models.DateTimeField(auto_now_add=True) reassigned_at = models.DateTimeField(null=True, blank=True) reassigned_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reassigned_command_roles' ) assignment_notes = models.TextField(blank=True, null=True) # Performance tracking decisions_made = models.PositiveIntegerField(default=0) communications_sent = models.PositiveIntegerField(default=0) last_activity = models.DateTimeField(null=True, blank=True) # Metadata created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='created_command_roles') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-assigned_at'] unique_together = ['incident', 'role_type', 'assigned_user'] indexes = [ models.Index(fields=['incident', 'role_type']), models.Index(fields=['assigned_user', 'status']), models.Index(fields=['status', 'assigned_at']), ] def __str__(self): return f"{self.get_role_type_display()} - {self.assigned_user.username if self.assigned_user else 'Unassigned'}" def can_make_decision(self, decision_area: str) -> bool: """Check if this role can make decisions in the given area""" return decision_area in self.decision_authority def reassign_role(self, new_user: User, reassigned_by: User, notes: str = None): """Reassign this role to a new user""" self.assigned_user = new_user self.reassigned_at = timezone.now() self.reassigned_by = reassigned_by self.assignment_notes = notes self.status = 'REASSIGNED' self.save() class TimelineEvent(models.Model): """Timeline reconstruction for postmortems (automatically ordered events + human notes)""" EVENT_TYPES = [ ('INCIDENT_CREATED', 'Incident Created'), ('INCIDENT_UPDATED', 'Incident Updated'), ('ASSIGNMENT_CHANGED', 'Assignment Changed'), ('STATUS_CHANGED', 'Status Changed'), ('SEVERITY_CHANGED', 'Severity Changed'), ('COMMENT_ADDED', 'Comment Added'), ('RUNBOOK_EXECUTED', 'Runbook Executed'), ('AUTO_REMEDIATION_ATTEMPTED', 'Auto-remediation Attempted'), ('SLA_BREACHED', 'SLA Breached'), ('ESCALATION_TRIGGERED', 'Escalation Triggered'), ('WAR_ROOM_CREATED', 'War Room Created'), ('CONFERENCE_STARTED', 'Conference Started'), ('COMMAND_ROLE_ASSIGNED', 'Command Role Assigned'), ('DECISION_MADE', 'Decision Made'), ('COMMUNICATION_SENT', 'Communication Sent'), ('EXTERNAL_INTEGRATION', 'External Integration'), ('MANUAL_EVENT', 'Manual Event'), ] SOURCE_TYPES = [ ('SYSTEM', 'System Generated'), ('USER', 'User Created'), ('INTEGRATION', 'External Integration'), ('AUTOMATION', 'Automation'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Related incident incident = models.ForeignKey( 'incident_intelligence.Incident', on_delete=models.CASCADE, related_name='timeline_events' ) # Event details event_type = models.CharField(max_length=30, choices=EVENT_TYPES) title = models.CharField(max_length=200) description = models.TextField() source_type = models.CharField(max_length=20, choices=SOURCE_TYPES, default='SYSTEM') # Timing event_time = models.DateTimeField(help_text="When the event occurred") created_at = models.DateTimeField(auto_now_add=True) # Related objects related_user = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_runbook_execution = models.ForeignKey( 'automation_orchestration.RunbookExecution', on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_auto_remediation = models.ForeignKey( 'automation_orchestration.AutoRemediationExecution', on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_sla_instance = models.ForeignKey( 'sla_oncall.SLAInstance', on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_escalation = models.ForeignKey( 'sla_oncall.EscalationInstance', on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_war_room = models.ForeignKey( WarRoom, on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_conference = models.ForeignKey( ConferenceBridge, on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) related_command_role = models.ForeignKey( IncidentCommandRole, on_delete=models.SET_NULL, null=True, blank=True, related_name='timeline_events' ) # Event data event_data = models.JSONField( default=dict, help_text="Additional data related to the event" ) tags = models.JSONField( default=list, help_text="Tags for categorization and filtering" ) # Postmortem relevance is_critical_event = models.BooleanField( default=False, help_text="Whether this event is critical for postmortem analysis" ) postmortem_notes = models.TextField( blank=True, null=True, help_text="Additional notes added during postmortem" ) # Metadata created_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_timeline_events' ) class Meta: ordering = ['event_time', 'created_at'] indexes = [ models.Index(fields=['incident', 'event_time']), models.Index(fields=['event_type', 'event_time']), models.Index(fields=['source_type', 'event_time']), models.Index(fields=['is_critical_event', 'event_time']), ] def __str__(self): return f"{self.event_time} - {self.title}" @classmethod def create_system_event(cls, incident, event_type: str, title: str, description: str, event_time: datetime = None, event_data: dict = None, **kwargs): """Create a system-generated timeline event""" if event_time is None: event_time = timezone.now() return cls.objects.create( incident=incident, event_type=event_type, title=title, description=description, event_time=event_time, source_type='SYSTEM', event_data=event_data or {}, **kwargs ) @classmethod def create_user_event(cls, incident, user: User, event_type: str, title: str, description: str, event_time: datetime = None, **kwargs): """Create a user-generated timeline event""" if event_time is None: event_time = timezone.now() return cls.objects.create( incident=incident, event_type=event_type, title=title, description=description, event_time=event_time, source_type='USER', related_user=user, created_by=user, **kwargs ) class WarRoomMessage(models.Model): """Messages within war rooms for collaboration""" MESSAGE_TYPES = [ ('TEXT', 'Text Message'), ('SYSTEM', 'System Message'), ('COMMAND', 'Command Message'), ('ALERT', 'Alert Message'), ('UPDATE', 'Status Update'), ('FILE', 'File Attachment'), ('BOT', 'Bot Message'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Related war room war_room = models.ForeignKey( WarRoom, on_delete=models.CASCADE, related_name='messages' ) # Message details message_type = models.CharField(max_length=20, choices=MESSAGE_TYPES, default='TEXT') content = models.TextField() # Sender information sender = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='war_room_messages' ) sender_name = models.CharField(max_length=100, help_text="Display name of sender") # Message metadata is_edited = models.BooleanField(default=False) edited_at = models.DateTimeField(null=True, blank=True) is_pinned = models.BooleanField(default=False, help_text="Whether this message is pinned") pinned_at = models.DateTimeField(null=True, blank=True) pinned_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='pinned_messages' ) reply_to = models.ForeignKey( 'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='replies' ) # File attachments attachments = models.JSONField( default=list, help_text="List of file attachments with metadata" ) # Mentions and notifications mentioned_users = models.ManyToManyField( User, blank=True, related_name='mentioned_in_messages', help_text="Users mentioned in this message" ) notification_sent = models.BooleanField(default=False) # Integration data external_message_id = models.CharField( max_length=255, blank=True, null=True, help_text="ID in external system (Slack, Teams, etc.)" ) external_data = models.JSONField( default=dict, help_text="Additional data from external system" ) # Encryption and security is_encrypted = models.BooleanField(default=False) encryption_key_id = models.CharField(max_length=255, blank=True, null=True) # Timestamps created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['created_at'] indexes = [ models.Index(fields=['war_room', 'created_at']), models.Index(fields=['sender', 'created_at']), models.Index(fields=['message_type', 'created_at']), models.Index(fields=['is_pinned', 'created_at']), ] def __str__(self): return f"{self.sender_name}: {self.content[:50]}..." def pin_message(self, user: User): """Pin this message""" self.is_pinned = True self.pinned_at = timezone.now() self.pinned_by = user self.save() def unpin_message(self): """Unpin this message""" self.is_pinned = False self.pinned_at = None self.pinned_by = None self.save() def add_reaction(self, user: User, emoji: str): """Add a reaction to this message""" reaction, created = MessageReaction.objects.get_or_create( message=self, user=user, emoji=emoji ) return reaction def remove_reaction(self, user: User, emoji: str): """Remove a reaction from this message""" MessageReaction.objects.filter( message=self, user=user, emoji=emoji ).delete() def get_reactions_summary(self): """Get summary of reactions for this message""" from django.db.models import Count return self.reactions.values('emoji').annotate(count=Count('emoji')).order_by('-count') class MessageReaction(models.Model): """Reactions to messages (👍, 🚨, ✅, etc.)""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) message = models.ForeignKey( WarRoomMessage, on_delete=models.CASCADE, related_name='reactions' ) user = models.ForeignKey(User, on_delete=models.CASCADE) emoji = models.CharField(max_length=10, help_text="Emoji reaction") created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ['message', 'user', 'emoji'] ordering = ['created_at'] indexes = [ models.Index(fields=['message', 'emoji']), models.Index(fields=['user', 'created_at']), ] def __str__(self): return f"{self.user.username} reacted {self.emoji} to message" class ChatFile(models.Model): """File attachments in chat messages with compliance integration""" FILE_TYPES = [ ('IMAGE', 'Image'), ('DOCUMENT', 'Document'), ('LOG', 'Log File'), ('SCREENSHOT', 'Screenshot'), ('EVIDENCE', 'Evidence'), ('OTHER', 'Other'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) message = models.ForeignKey( WarRoomMessage, on_delete=models.CASCADE, related_name='chat_files' ) # File details filename = models.CharField(max_length=255) original_filename = models.CharField(max_length=255) file_type = models.CharField(max_length=20, choices=FILE_TYPES) file_size = models.PositiveIntegerField(help_text="File size in bytes") mime_type = models.CharField(max_length=100) # Storage file_path = models.CharField(max_length=500, help_text="Path to stored file") file_url = models.URLField(blank=True, null=True, help_text="Public URL for file access") # Security and compliance data_classification = models.ForeignKey( 'security.DataClassification', on_delete=models.SET_NULL, null=True, blank=True, help_text="Data classification level for this file" ) is_encrypted = models.BooleanField(default=False) encryption_key_id = models.CharField(max_length=255, blank=True, null=True) # Chain of custody file_hash = models.CharField(max_length=64, help_text="SHA-256 hash of file") uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) uploaded_at = models.DateTimeField(auto_now_add=True) # Access control access_log = models.JSONField( default=list, help_text="Log of who accessed this file and when" ) class Meta: ordering = ['-uploaded_at'] indexes = [ models.Index(fields=['message', 'file_type']), models.Index(fields=['data_classification']), models.Index(fields=['uploaded_at']), ] def __str__(self): return f"{self.original_filename} ({self.file_type})" def log_access(self, user: User): """Log file access for audit trail""" access_entry = { 'user_id': str(user.id), 'username': user.username, 'timestamp': timezone.now().isoformat(), 'action': 'access' } self.access_log.append(access_entry) self.save(update_fields=['access_log']) class ChatCommand(models.Model): """ChatOps commands for automation integration""" COMMAND_TYPES = [ ('STATUS', 'Status Check'), ('RUNBOOK', 'Execute Runbook'), ('ESCALATE', 'Escalate Incident'), ('ASSIGN', 'Assign Incident'), ('UPDATE', 'Update Status'), ('CUSTOM', 'Custom Command'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) message = models.ForeignKey( WarRoomMessage, on_delete=models.CASCADE, related_name='chat_commands' ) # Command details command_type = models.CharField(max_length=20, choices=COMMAND_TYPES) command_text = models.CharField(max_length=500, help_text="Full command text") parameters = models.JSONField( default=dict, help_text="Parsed command parameters" ) # Execution executed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) executed_at = models.DateTimeField(null=True, blank=True) execution_status = models.CharField( max_length=20, choices=[ ('PENDING', 'Pending'), ('EXECUTING', 'Executing'), ('SUCCESS', 'Success'), ('FAILED', 'Failed'), ('CANCELLED', 'Cancelled'), ], default='PENDING' ) execution_result = models.JSONField( default=dict, help_text="Result of command execution" ) error_message = models.TextField(blank=True, null=True) # Integration automation_execution = models.ForeignKey( 'automation_orchestration.RunbookExecution', on_delete=models.SET_NULL, null=True, blank=True, related_name='chat_commands' ) class Meta: ordering = ['-executed_at'] indexes = [ models.Index(fields=['command_type', 'execution_status']), models.Index(fields=['executed_by', 'executed_at']), ] def __str__(self): return f"{self.command_type}: {self.command_text[:50]}" def execute_command(self, user: User): """Execute the chat command""" self.executed_by = user self.executed_at = timezone.now() self.execution_status = 'EXECUTING' self.save() try: # Execute based on command type if self.command_type == 'STATUS': result = self._execute_status_command() elif self.command_type == 'RUNBOOK': result = self._execute_runbook_command() elif self.command_type == 'ESCALATE': result = self._execute_escalate_command() else: result = {'error': 'Unknown command type'} self.execution_result = result self.execution_status = 'SUCCESS' if 'error' not in result else 'FAILED' except Exception as e: self.execution_status = 'FAILED' self.error_message = str(e) self.execution_result = {'error': str(e)} self.save() return self.execution_result def _execute_status_command(self): """Execute status check command""" incident = self.message.war_room.incident return { 'incident_id': str(incident.id), 'title': incident.title, 'status': incident.status, 'severity': incident.severity, 'assigned_to': incident.assigned_to.username if incident.assigned_to else None, 'created_at': incident.created_at.isoformat(), 'updated_at': incident.updated_at.isoformat(), } def _execute_runbook_command(self): """Execute runbook command""" from .services.automation_commands import AutomationCommandService # Parse runbook name from parameters args = self.parameters.get('args', []) if not args: return {'error': 'Runbook name is required. Usage: /run playbook '} runbook_name = ' '.join(args) return AutomationCommandService.execute_runbook_command(self, runbook_name, self.executed_by) def _execute_escalate_command(self): """Execute escalation command""" from .services.sla_notifications import SLANotificationService # Get escalation policies for this incident incident = self.message.war_room.incident escalation_policies = incident.escalation_instances.filter(status='PENDING') if not escalation_policies.exists(): return {'message': 'No pending escalations found for this incident'} # Trigger escalation escalation = escalation_policies.first() escalation.status = 'TRIGGERED' escalation.triggered_at = timezone.now() escalation.save() # Send notification SLANotificationService.send_escalation_notification(escalation) return { 'success': True, 'escalation_id': str(escalation.id), 'policy_name': escalation.escalation_policy.name, 'level': escalation.escalation_level } class ChatBot(models.Model): """AI assistant bot for chat rooms""" BOT_TYPES = [ ('INCIDENT_ASSISTANT', 'Incident Assistant'), ('KNOWLEDGE_BOT', 'Knowledge Bot'), ('AUTOMATION_BOT', 'Automation Bot'), ('COMPLIANCE_BOT', 'Compliance Bot'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100) bot_type = models.CharField(max_length=30, choices=BOT_TYPES) description = models.TextField() # Configuration is_active = models.BooleanField(default=True) auto_respond = models.BooleanField(default=False) response_triggers = models.JSONField( default=list, help_text="Keywords that trigger bot responses" ) # Integration knowledge_base = models.ForeignKey( 'knowledge_learning.KnowledgeBaseArticle', on_delete=models.SET_NULL, null=True, blank=True, help_text="Knowledge base article for bot responses" ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['name'] indexes = [ models.Index(fields=['bot_type', 'is_active']), ] def __str__(self): return f"{self.name} ({self.get_bot_type_display()})" def generate_response(self, message: WarRoomMessage, context: dict = None): """Generate AI response to a message""" from .services.ai_assistant import AIAssistantService return AIAssistantService.generate_response(self, message, context) class IncidentDecision(models.Model): """Decisions made during incident response""" DECISION_TYPES = [ ('TECHNICAL', 'Technical Decision'), ('BUSINESS', 'Business Decision'), ('COMMUNICATION', 'Communication Decision'), ('ESCALATION', 'Escalation Decision'), ('RESOURCE', 'Resource Allocation'), ('TIMELINE', 'Timeline Decision'), ] STATUS_CHOICES = [ ('PENDING', 'Pending'), ('APPROVED', 'Approved'), ('REJECTED', 'Rejected'), ('IMPLEMENTED', 'Implemented'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Related incident and command role incident = models.ForeignKey( 'incident_intelligence.Incident', on_delete=models.CASCADE, related_name='decisions' ) command_role = models.ForeignKey( IncidentCommandRole, on_delete=models.CASCADE, related_name='decisions' ) # Decision details decision_type = models.CharField(max_length=20, choices=DECISION_TYPES) title = models.CharField(max_length=200) description = models.TextField() rationale = models.TextField(help_text="Reasoning behind the decision") # Decision status status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING') requires_approval = models.BooleanField(default=False) approved_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_decisions' ) approved_at = models.DateTimeField(null=True, blank=True) # Implementation implementation_notes = models.TextField(blank=True, null=True) implemented_at = models.DateTimeField(null=True, blank=True) implemented_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='implemented_decisions' ) # Impact tracking impact_assessment = models.TextField(blank=True, null=True) success_metrics = models.JSONField( default=list, help_text="Metrics to measure decision success" ) # Timestamps created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['incident', 'status']), models.Index(fields=['command_role', 'decision_type']), models.Index(fields=['status', 'created_at']), ] def __str__(self): return f"Decision: {self.title} ({self.get_status_display()})" def approve(self, approver: User): """Approve the decision""" self.status = 'APPROVED' self.approved_by = approver self.approved_at = timezone.now() self.save() # Create timeline event TimelineEvent.create_user_event( incident=self.incident, user=approver, event_type='DECISION_MADE', title=f"Decision Approved: {self.title}", description=f"Decision '{self.title}' was approved by {approver.username}", related_command_role=self.command_role, event_data={'decision_id': str(self.id), 'action': 'approved'} ) def implement(self, implementer: User, notes: str = None): """Mark decision as implemented""" self.status = 'IMPLEMENTED' self.implemented_by = implementer self.implemented_at = timezone.now() if notes: self.implementation_notes = notes self.save() # Create timeline event TimelineEvent.create_user_event( incident=self.incident, user=implementer, event_type='DECISION_MADE', title=f"Decision Implemented: {self.title}", description=f"Decision '{self.title}' was implemented by {implementer.username}", related_command_role=self.command_role, event_data={'decision_id': str(self.id), 'action': 'implemented'} )