Files
ETB/ETB-API/knowledge_learning/models.py
Iliyan Angelov 6b247e5b9f Updates
2025-09-19 11:58:53 +03:00

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}"