509 lines
20 KiB
Python
509 lines
20 KiB
Python
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
|
|
import uuid
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class Postmortem(models.Model):
|
|
"""Automated postmortem generation and management"""
|
|
|
|
STATUS_CHOICES = [
|
|
('DRAFT', 'Draft'),
|
|
('IN_REVIEW', 'In Review'),
|
|
('APPROVED', 'Approved'),
|
|
('PUBLISHED', 'Published'),
|
|
('ARCHIVED', 'Archived'),
|
|
]
|
|
|
|
SEVERITY_CHOICES = [
|
|
('LOW', 'Low'),
|
|
('MEDIUM', 'Medium'),
|
|
('HIGH', 'High'),
|
|
('CRITICAL', 'Critical'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
title = models.CharField(max_length=200)
|
|
incident = models.ForeignKey(
|
|
'incident_intelligence.Incident',
|
|
on_delete=models.CASCADE,
|
|
related_name='postmortems',
|
|
help_text="Primary incident this postmortem is about"
|
|
)
|
|
|
|
# Postmortem content
|
|
executive_summary = models.TextField(help_text="High-level summary for executives")
|
|
timeline = models.JSONField(default=list, help_text="Detailed timeline of events")
|
|
root_cause_analysis = models.TextField(help_text="Analysis of root causes")
|
|
impact_assessment = models.TextField(help_text="Assessment of business and technical impact")
|
|
lessons_learned = models.TextField(help_text="Key lessons learned from the incident")
|
|
action_items = models.JSONField(default=list, help_text="List of action items to prevent recurrence")
|
|
|
|
# Automated generation
|
|
is_automated = models.BooleanField(default=True, help_text="Whether this postmortem was auto-generated")
|
|
generation_confidence = models.FloatField(
|
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
|
null=True, blank=True,
|
|
help_text="AI confidence in postmortem quality (0.0-1.0)"
|
|
)
|
|
auto_generated_sections = models.JSONField(
|
|
default=list,
|
|
help_text="List of sections that were auto-generated"
|
|
)
|
|
|
|
# Status and workflow
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT')
|
|
severity = models.CharField(max_length=20, choices=SEVERITY_CHOICES)
|
|
|
|
# Ownership and review
|
|
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='owned_postmortems')
|
|
reviewers = models.ManyToManyField(User, related_name='reviewed_postmortems', blank=True)
|
|
approver = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_postmortems')
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
published_at = models.DateTimeField(null=True, blank=True)
|
|
due_date = models.DateTimeField(null=True, blank=True, help_text="When this postmortem should be completed")
|
|
|
|
# Related incidents and context
|
|
related_incidents = models.ManyToManyField(
|
|
'incident_intelligence.Incident',
|
|
related_name='related_postmortems',
|
|
blank=True,
|
|
help_text="Other incidents related to this postmortem"
|
|
)
|
|
affected_services = models.JSONField(default=list, help_text="Services affected by the incident")
|
|
affected_teams = models.JSONField(default=list, help_text="Teams involved in the incident")
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['status', 'severity']),
|
|
models.Index(fields=['incident', 'status']),
|
|
models.Index(fields=['created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Postmortem: {self.title}"
|
|
|
|
@property
|
|
def is_overdue(self):
|
|
"""Check if postmortem is overdue"""
|
|
if self.due_date and self.status not in ['APPROVED', 'PUBLISHED']:
|
|
return timezone.now() > self.due_date
|
|
return False
|
|
|
|
def get_completion_percentage(self):
|
|
"""Calculate completion percentage based on filled sections"""
|
|
sections = [
|
|
self.executive_summary,
|
|
self.timeline,
|
|
self.root_cause_analysis,
|
|
self.impact_assessment,
|
|
self.lessons_learned,
|
|
self.action_items,
|
|
]
|
|
filled_sections = sum(1 for section in sections if section)
|
|
return (filled_sections / len(sections)) * 100
|
|
|
|
|
|
class KnowledgeBaseArticle(models.Model):
|
|
"""Knowledge base articles for incident resolution and learning"""
|
|
|
|
ARTICLE_TYPE_CHOICES = [
|
|
('RUNBOOK', 'Runbook'),
|
|
('TROUBLESHOOTING', 'Troubleshooting Guide'),
|
|
('BEST_PRACTICE', 'Best Practice'),
|
|
('LESSON_LEARNED', 'Lesson Learned'),
|
|
('PROCEDURE', 'Procedure'),
|
|
('REFERENCE', 'Reference'),
|
|
('WIKI', 'Wiki Article'),
|
|
]
|
|
|
|
STATUS_CHOICES = [
|
|
('DRAFT', 'Draft'),
|
|
('REVIEW', 'Under Review'),
|
|
('APPROVED', 'Approved'),
|
|
('PUBLISHED', 'Published'),
|
|
('DEPRECATED', 'Deprecated'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
title = models.CharField(max_length=200)
|
|
slug = models.SlugField(unique=True, help_text="URL-friendly identifier")
|
|
|
|
# Content
|
|
content = models.TextField(help_text="Main article content")
|
|
summary = models.TextField(help_text="Brief summary of the article")
|
|
tags = models.JSONField(default=list, help_text="Tags for categorization and search")
|
|
|
|
# Classification
|
|
article_type = models.CharField(max_length=20, choices=ARTICLE_TYPE_CHOICES)
|
|
category = models.CharField(max_length=100, help_text="Primary category")
|
|
subcategory = models.CharField(max_length=100, blank=True, null=True)
|
|
|
|
# Related services and components
|
|
related_services = models.JSONField(default=list, help_text="Services this article relates to")
|
|
related_components = models.JSONField(default=list, help_text="Components this article relates to")
|
|
|
|
# Status and workflow
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT')
|
|
is_featured = models.BooleanField(default=False, help_text="Whether this is a featured article")
|
|
view_count = models.PositiveIntegerField(default=0, help_text="Number of times this article has been viewed")
|
|
|
|
# Ownership and maintenance
|
|
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='authored_articles')
|
|
last_updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='updated_articles')
|
|
maintainer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='maintained_articles')
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
last_reviewed = models.DateTimeField(null=True, blank=True)
|
|
next_review_due = models.DateTimeField(null=True, blank=True)
|
|
|
|
# Related incidents and postmortems
|
|
related_incidents = models.ManyToManyField(
|
|
'incident_intelligence.Incident',
|
|
related_name='knowledge_articles',
|
|
blank=True,
|
|
help_text="Incidents that led to or are related to this article"
|
|
)
|
|
source_postmortems = models.ManyToManyField(
|
|
Postmortem,
|
|
related_name='generated_articles',
|
|
blank=True,
|
|
help_text="Postmortems that generated this article"
|
|
)
|
|
|
|
# External integrations
|
|
confluence_url = models.URLField(blank=True, null=True, help_text="Link to Confluence page")
|
|
wiki_url = models.URLField(blank=True, null=True, help_text="Link to wiki page")
|
|
external_references = models.JSONField(default=list, help_text="External reference links")
|
|
|
|
# Search and discovery
|
|
search_keywords = models.JSONField(default=list, help_text="Keywords for search optimization")
|
|
difficulty_level = models.CharField(max_length=20, choices=[
|
|
('BEGINNER', 'Beginner'),
|
|
('INTERMEDIATE', 'Intermediate'),
|
|
('ADVANCED', 'Advanced'),
|
|
('EXPERT', 'Expert'),
|
|
], default='INTERMEDIATE')
|
|
|
|
class Meta:
|
|
ordering = ['-updated_at', '-created_at']
|
|
indexes = [
|
|
models.Index(fields=['article_type', 'status']),
|
|
models.Index(fields=['category', 'subcategory']),
|
|
models.Index(fields=['status', 'is_featured']),
|
|
models.Index(fields=['created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"KB Article: {self.title}"
|
|
|
|
def increment_view_count(self):
|
|
"""Increment the view count"""
|
|
self.view_count += 1
|
|
self.save(update_fields=['view_count'])
|
|
|
|
def is_due_for_review(self):
|
|
"""Check if article is due for review"""
|
|
if self.next_review_due:
|
|
return timezone.now() > self.next_review_due
|
|
return False
|
|
|
|
|
|
class IncidentRecommendation(models.Model):
|
|
"""Recommendation engine for suggesting similar incidents and solutions"""
|
|
|
|
RECOMMENDATION_TYPE_CHOICES = [
|
|
('SIMILAR_INCIDENT', 'Similar Incident'),
|
|
('SOLUTION', 'Solution/Resolution'),
|
|
('KNOWLEDGE_ARTICLE', 'Knowledge Article'),
|
|
('RUNBOOK', 'Runbook'),
|
|
('EXPERT', 'Expert/Team'),
|
|
('PREVENTION', 'Prevention Strategy'),
|
|
]
|
|
|
|
CONFIDENCE_LEVEL_CHOICES = [
|
|
('LOW', 'Low'),
|
|
('MEDIUM', 'Medium'),
|
|
('HIGH', 'High'),
|
|
('VERY_HIGH', 'Very High'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
incident = models.ForeignKey(
|
|
'incident_intelligence.Incident',
|
|
on_delete=models.CASCADE,
|
|
related_name='recommendations',
|
|
help_text="Incident for which this recommendation is made"
|
|
)
|
|
|
|
# Recommendation details
|
|
recommendation_type = models.CharField(max_length=20, choices=RECOMMENDATION_TYPE_CHOICES)
|
|
title = models.CharField(max_length=200)
|
|
description = models.TextField(help_text="Description of the recommendation")
|
|
|
|
# Similarity and confidence
|
|
similarity_score = models.FloatField(
|
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
|
help_text="Similarity score between current and recommended incident (0.0-1.0)"
|
|
)
|
|
confidence_level = models.CharField(max_length=20, choices=CONFIDENCE_LEVEL_CHOICES)
|
|
confidence_score = models.FloatField(
|
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
|
help_text="AI confidence in this recommendation (0.0-1.0)"
|
|
)
|
|
|
|
# Related objects
|
|
related_incident = models.ForeignKey(
|
|
'incident_intelligence.Incident',
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
related_name='recommended_for',
|
|
help_text="Related incident that this recommendation is based on"
|
|
)
|
|
knowledge_article = models.ForeignKey(
|
|
KnowledgeBaseArticle,
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
help_text="Related knowledge base article"
|
|
)
|
|
suggested_expert = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
help_text="Suggested expert who can help with this incident"
|
|
)
|
|
|
|
# Recommendation content
|
|
suggested_actions = models.JSONField(default=list, help_text="Suggested actions to take")
|
|
expected_outcome = models.TextField(blank=True, null=True, help_text="Expected outcome of following this recommendation")
|
|
time_to_implement = models.DurationField(null=True, blank=True, help_text="Estimated time to implement")
|
|
|
|
# Usage tracking
|
|
is_applied = models.BooleanField(default=False, help_text="Whether this recommendation was applied")
|
|
applied_at = models.DateTimeField(null=True, blank=True)
|
|
applied_by = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='applied_recommendations'
|
|
)
|
|
effectiveness_rating = models.PositiveIntegerField(
|
|
null=True, blank=True,
|
|
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
|
help_text="User rating of recommendation effectiveness (1-5)"
|
|
)
|
|
|
|
# AI analysis
|
|
reasoning = models.TextField(help_text="AI explanation for why this recommendation was made")
|
|
matching_factors = models.JSONField(default=list, help_text="Factors that led to this recommendation")
|
|
model_version = models.CharField(max_length=50, default='v1.0')
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['-confidence_score', '-similarity_score']
|
|
indexes = [
|
|
models.Index(fields=['incident', 'recommendation_type']),
|
|
models.Index(fields=['confidence_score', 'similarity_score']),
|
|
models.Index(fields=['is_applied']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Recommendation: {self.title} for {self.incident.title}"
|
|
|
|
|
|
class LearningPattern(models.Model):
|
|
"""Patterns learned from incidents and postmortems"""
|
|
|
|
PATTERN_TYPE_CHOICES = [
|
|
('ROOT_CAUSE', 'Root Cause Pattern'),
|
|
('RESOLUTION', 'Resolution Pattern'),
|
|
('PREVENTION', 'Prevention Pattern'),
|
|
('ESCALATION', 'Escalation Pattern'),
|
|
('COMMUNICATION', 'Communication Pattern'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
name = models.CharField(max_length=200)
|
|
pattern_type = models.CharField(max_length=20, choices=PATTERN_TYPE_CHOICES)
|
|
description = models.TextField()
|
|
|
|
# Pattern characteristics
|
|
frequency = models.PositiveIntegerField(default=1, help_text="How many times this pattern has been observed")
|
|
success_rate = models.FloatField(
|
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
|
help_text="Success rate when this pattern is applied (0.0-1.0)"
|
|
)
|
|
confidence_score = models.FloatField(
|
|
validators=[MinValueValidator(0.0), MaxValueValidator(1.0)],
|
|
help_text="Confidence in this pattern's validity (0.0-1.0)"
|
|
)
|
|
|
|
# Pattern details
|
|
triggers = models.JSONField(default=list, help_text="Conditions that trigger this pattern")
|
|
actions = models.JSONField(default=list, help_text="Actions associated with this pattern")
|
|
outcomes = models.JSONField(default=list, help_text="Expected outcomes of this pattern")
|
|
|
|
# Related data
|
|
source_incidents = models.ManyToManyField(
|
|
'incident_intelligence.Incident',
|
|
related_name='learning_patterns',
|
|
help_text="Incidents that contributed to this pattern"
|
|
)
|
|
source_postmortems = models.ManyToManyField(
|
|
Postmortem,
|
|
related_name='learning_patterns',
|
|
help_text="Postmortems that contributed to this pattern"
|
|
)
|
|
|
|
# Pattern validation
|
|
is_validated = models.BooleanField(default=False, help_text="Whether this pattern has been validated by experts")
|
|
validated_by = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='validated_patterns'
|
|
)
|
|
validation_notes = models.TextField(blank=True, null=True)
|
|
|
|
# Usage tracking
|
|
times_applied = models.PositiveIntegerField(default=0, help_text="Number of times this pattern has been applied")
|
|
last_applied = models.DateTimeField(null=True, blank=True)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['-confidence_score', '-frequency']
|
|
indexes = [
|
|
models.Index(fields=['pattern_type', 'confidence_score']),
|
|
models.Index(fields=['is_validated']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Pattern: {self.name} ({self.pattern_type})"
|
|
|
|
|
|
class KnowledgeBaseUsage(models.Model):
|
|
"""Track usage of knowledge base articles and recommendations"""
|
|
|
|
USAGE_TYPE_CHOICES = [
|
|
('VIEW', 'Article View'),
|
|
('APPLY', 'Recommendation Applied'),
|
|
('RATE', 'Rating Given'),
|
|
('SHARE', 'Shared'),
|
|
('BOOKMARK', 'Bookmarked'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='knowledge_usage')
|
|
usage_type = models.CharField(max_length=20, choices=USAGE_TYPE_CHOICES)
|
|
|
|
# Related objects
|
|
knowledge_article = models.ForeignKey(
|
|
KnowledgeBaseArticle,
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
related_name='usage_logs'
|
|
)
|
|
recommendation = models.ForeignKey(
|
|
IncidentRecommendation,
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
related_name='usage_logs'
|
|
)
|
|
incident = models.ForeignKey(
|
|
'incident_intelligence.Incident',
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
related_name='knowledge_usage'
|
|
)
|
|
|
|
# Usage context
|
|
context = models.JSONField(default=dict, help_text="Additional context about the usage")
|
|
session_id = models.CharField(max_length=100, blank=True, null=True)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['user', 'usage_type']),
|
|
models.Index(fields=['created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.usage_type} by {self.user.username}"
|
|
|
|
|
|
class AutomatedPostmortemGeneration(models.Model):
|
|
"""Track automated postmortem generation attempts and results"""
|
|
|
|
STATUS_CHOICES = [
|
|
('PENDING', 'Pending'),
|
|
('PROCESSING', 'Processing'),
|
|
('COMPLETED', 'Completed'),
|
|
('FAILED', 'Failed'),
|
|
('REVIEW_REQUIRED', 'Review Required'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
incident = models.ForeignKey(
|
|
'incident_intelligence.Incident',
|
|
on_delete=models.CASCADE,
|
|
related_name='postmortem_generations'
|
|
)
|
|
|
|
# Generation details
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING')
|
|
generation_trigger = models.CharField(max_length=50, help_text="What triggered the generation")
|
|
|
|
# Input data
|
|
incident_data = models.JSONField(help_text="Incident data used for generation")
|
|
timeline_data = models.JSONField(help_text="Timeline data used for generation")
|
|
log_data = models.JSONField(default=list, help_text="Log data used for generation")
|
|
|
|
# Generation results
|
|
generated_content = models.JSONField(null=True, blank=True, help_text="Generated postmortem content")
|
|
confidence_scores = models.JSONField(default=dict, help_text="Confidence scores for different sections")
|
|
quality_metrics = models.JSONField(default=dict, help_text="Quality metrics for generated content")
|
|
|
|
# Postmortem relationship
|
|
generated_postmortem = models.OneToOneField(
|
|
Postmortem,
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True,
|
|
related_name='generation_log'
|
|
)
|
|
|
|
# Processing details
|
|
processing_time = models.FloatField(null=True, blank=True, help_text="Time taken for generation in seconds")
|
|
model_version = models.CharField(max_length=50, default='v1.0')
|
|
error_message = models.TextField(blank=True, null=True)
|
|
|
|
# Timestamps
|
|
started_at = models.DateTimeField(auto_now_add=True)
|
|
completed_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-started_at']
|
|
indexes = [
|
|
models.Index(fields=['status', 'started_at']),
|
|
models.Index(fields=['incident', 'status']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Postmortem Generation for {self.incident.title} - {self.status}" |