Imporve enterprise
This commit is contained in:
365
services/tenant/app/services/network_alerts_service.py
Normal file
365
services/tenant/app/services/network_alerts_service.py
Normal file
@@ -0,0 +1,365 @@
|
||||
# services/tenant/app/services/network_alerts_service.py
|
||||
"""
|
||||
Network Alerts Service
|
||||
Business logic for aggregating and managing alerts across enterprise networks
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class NetworkAlertsService:
|
||||
"""
|
||||
Service for aggregating and managing alerts across enterprise networks
|
||||
"""
|
||||
|
||||
def __init__(self, tenant_client, alerts_client):
|
||||
self.tenant_client = tenant_client
|
||||
self.alerts_client = alerts_client
|
||||
|
||||
async def get_child_tenants(self, parent_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all child tenants for a parent tenant
|
||||
"""
|
||||
try:
|
||||
# Get child tenants from tenant service
|
||||
children = await self.tenant_client.get_child_tenants(parent_id)
|
||||
|
||||
# Enrich with tenant details
|
||||
enriched_children = []
|
||||
for child in children:
|
||||
child_details = await self.tenant_client.get_tenant(child['id'])
|
||||
enriched_children.append({
|
||||
'id': child['id'],
|
||||
'name': child_details.get('name', f"Outlet {child['id']}"),
|
||||
'subdomain': child_details.get('subdomain'),
|
||||
'city': child_details.get('city')
|
||||
})
|
||||
|
||||
return enriched_children
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get child tenants", parent_id=parent_id, error=str(e))
|
||||
raise Exception(f"Failed to get child tenants: {str(e)}")
|
||||
|
||||
async def get_alerts_for_tenant(self, tenant_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get alerts for a specific tenant
|
||||
"""
|
||||
try:
|
||||
# In a real implementation, this would call the alert service
|
||||
# For demo purposes, we'll simulate some alert data
|
||||
|
||||
# Simulate different types of alerts based on tenant type
|
||||
simulated_alerts = []
|
||||
|
||||
# Generate some sample alerts
|
||||
alert_types = ['inventory', 'production', 'delivery', 'equipment', 'quality']
|
||||
severities = ['critical', 'high', 'medium', 'low']
|
||||
|
||||
for i in range(3): # Generate 3 sample alerts per tenant
|
||||
alert = {
|
||||
'alert_id': str(uuid.uuid4()),
|
||||
'tenant_id': tenant_id,
|
||||
'alert_type': alert_types[i % len(alert_types)],
|
||||
'severity': severities[i % len(severities)],
|
||||
'title': f"{alert_types[i % len(alert_types)].title()} Alert Detected",
|
||||
'message': f"Sample {alert_types[i % len(alert_types)]} alert for tenant {tenant_id}",
|
||||
'timestamp': (datetime.now() - timedelta(hours=i)).isoformat(),
|
||||
'status': 'active' if i < 2 else 'resolved',
|
||||
'source_system': f"{alert_types[i % len(alert_types)]}-service",
|
||||
'related_entity_id': f"entity-{i+1}",
|
||||
'related_entity_type': alert_types[i % len(alert_types)]
|
||||
}
|
||||
simulated_alerts.append(alert)
|
||||
|
||||
return simulated_alerts
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get alerts for tenant", tenant_id=tenant_id, error=str(e))
|
||||
raise Exception(f"Failed to get alerts: {str(e)}")
|
||||
|
||||
async def get_network_alerts(self, parent_id: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get all alerts across the network
|
||||
"""
|
||||
try:
|
||||
# Get all child tenants
|
||||
child_tenants = await self.get_child_tenants(parent_id)
|
||||
|
||||
# Aggregate alerts from all child tenants
|
||||
all_alerts = []
|
||||
|
||||
for child in child_tenants:
|
||||
child_id = child['id']
|
||||
child_alerts = await self.get_alerts_for_tenant(child_id)
|
||||
all_alerts.extend(child_alerts)
|
||||
|
||||
return all_alerts
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get network alerts", parent_id=parent_id, error=str(e))
|
||||
raise Exception(f"Failed to get network alerts: {str(e)}")
|
||||
|
||||
async def detect_alert_correlations(
|
||||
self,
|
||||
alerts: List[Dict[str, Any]],
|
||||
min_correlation_strength: float = 0.7
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Detect correlations between alerts
|
||||
"""
|
||||
try:
|
||||
# Simple correlation detection (in real implementation, this would be more sophisticated)
|
||||
correlations = []
|
||||
|
||||
# Group alerts by type and time proximity
|
||||
alert_groups = {}
|
||||
|
||||
for alert in alerts:
|
||||
alert_type = alert['alert_type']
|
||||
timestamp = alert['timestamp']
|
||||
|
||||
# Use timestamp as key for grouping (simplified)
|
||||
if alert_type not in alert_groups:
|
||||
alert_groups[alert_type] = []
|
||||
|
||||
alert_groups[alert_type].append(alert)
|
||||
|
||||
# Create correlation groups
|
||||
for alert_type, group in alert_groups.items():
|
||||
if len(group) >= 2: # Only create correlations for groups with 2+ alerts
|
||||
primary_alert = group[0]
|
||||
related_alerts = group[1:]
|
||||
|
||||
correlation = {
|
||||
'correlation_id': str(uuid.uuid4()),
|
||||
'primary_alert': primary_alert,
|
||||
'related_alerts': related_alerts,
|
||||
'correlation_type': 'temporal',
|
||||
'correlation_strength': 0.85,
|
||||
'impact_analysis': f"Multiple {alert_type} alerts detected within short timeframe"
|
||||
}
|
||||
|
||||
if correlation['correlation_strength'] >= min_correlation_strength:
|
||||
correlations.append(correlation)
|
||||
|
||||
return correlations
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to detect alert correlations", error=str(e))
|
||||
raise Exception(f"Failed to detect correlations: {str(e)}")
|
||||
|
||||
async def acknowledge_alert(self, parent_id: str, alert_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Acknowledge an alert
|
||||
"""
|
||||
try:
|
||||
# In a real implementation, this would update the alert status
|
||||
# For demo purposes, we'll simulate the operation
|
||||
|
||||
logger.info("Alert acknowledged", parent_id=parent_id, alert_id=alert_id)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'alert_id': alert_id,
|
||||
'status': 'acknowledged'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to acknowledge alert", parent_id=parent_id, alert_id=alert_id, error=str(e))
|
||||
raise Exception(f"Failed to acknowledge alert: {str(e)}")
|
||||
|
||||
async def resolve_alert(self, parent_id: str, alert_id: str, resolution_notes: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve an alert
|
||||
"""
|
||||
try:
|
||||
# In a real implementation, this would update the alert status
|
||||
# For demo purposes, we'll simulate the operation
|
||||
|
||||
logger.info("Alert resolved", parent_id=parent_id, alert_id=alert_id, notes=resolution_notes)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'alert_id': alert_id,
|
||||
'status': 'resolved',
|
||||
'resolution_notes': resolution_notes
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to resolve alert", parent_id=parent_id, alert_id=alert_id, error=str(e))
|
||||
raise Exception(f"Failed to resolve alert: {str(e)}")
|
||||
|
||||
async def get_alert_trends(self, parent_id: str, days: int = 30) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get alert trends over time
|
||||
"""
|
||||
try:
|
||||
# Simulate trend data
|
||||
trends = []
|
||||
end_date = datetime.now()
|
||||
|
||||
# Generate daily trend data
|
||||
for i in range(days):
|
||||
date = end_date - timedelta(days=i)
|
||||
|
||||
# Simulate varying alert counts with weekly pattern
|
||||
base_count = 5
|
||||
weekly_variation = int((i % 7) * 1.5) # Higher on weekdays
|
||||
daily_noise = (i % 3 - 1) # Daily noise
|
||||
|
||||
alert_count = max(1, base_count + weekly_variation + daily_noise)
|
||||
|
||||
trends.append({
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
'total_alerts': alert_count,
|
||||
'critical_alerts': max(0, int(alert_count * 0.1)),
|
||||
'high_alerts': max(0, int(alert_count * 0.2)),
|
||||
'medium_alerts': max(0, int(alert_count * 0.4)),
|
||||
'low_alerts': max(0, int(alert_count * 0.3))
|
||||
})
|
||||
|
||||
# Sort by date (oldest first)
|
||||
trends.sort(key=lambda x: x['date'])
|
||||
|
||||
return trends
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get alert trends", parent_id=parent_id, error=str(e))
|
||||
raise Exception(f"Failed to get alert trends: {str(e)}")
|
||||
|
||||
async def get_prioritized_alerts(self, parent_id: str, limit: int = 10) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get prioritized alerts based on impact and urgency
|
||||
"""
|
||||
try:
|
||||
# Get all network alerts
|
||||
all_alerts = await self.get_network_alerts(parent_id)
|
||||
|
||||
if not all_alerts:
|
||||
return []
|
||||
|
||||
# Simple prioritization (in real implementation, this would use ML)
|
||||
# Priority based on severity and recency
|
||||
severity_scores = {'critical': 4, 'high': 3, 'medium': 2, 'low': 1}
|
||||
|
||||
for alert in all_alerts:
|
||||
severity_score = severity_scores.get(alert['severity'], 1)
|
||||
# Add recency score (newer alerts get higher priority)
|
||||
timestamp = datetime.fromisoformat(alert['timestamp'])
|
||||
recency_score = min(3, (datetime.now() - timestamp).days + 1)
|
||||
|
||||
alert['priority_score'] = severity_score * recency_score
|
||||
|
||||
# Sort by priority score (highest first)
|
||||
all_alerts.sort(key=lambda x: x['priority_score'], reverse=True)
|
||||
|
||||
# Return top N alerts
|
||||
prioritized = all_alerts[:limit]
|
||||
|
||||
# Remove priority score from response
|
||||
for alert in prioritized:
|
||||
alert.pop('priority_score', None)
|
||||
|
||||
return prioritized
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get prioritized alerts", parent_id=parent_id, error=str(e))
|
||||
raise Exception(f"Failed to get prioritized alerts: {str(e)}")
|
||||
|
||||
|
||||
# Helper class for alert analysis
|
||||
class AlertAnalyzer:
|
||||
"""
|
||||
Helper class for analyzing alert patterns
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_alert_severity_score(alert: Dict[str, Any]) -> float:
|
||||
"""
|
||||
Calculate severity score for an alert
|
||||
"""
|
||||
severity_scores = {'critical': 1.0, 'high': 0.75, 'medium': 0.5, 'low': 0.25}
|
||||
return severity_scores.get(alert['severity'], 0.25)
|
||||
|
||||
@staticmethod
|
||||
def detect_alert_patterns(alerts: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
Detect patterns in alert data
|
||||
"""
|
||||
if not alerts:
|
||||
return {'patterns': [], 'anomalies': []}
|
||||
|
||||
patterns = []
|
||||
anomalies = []
|
||||
|
||||
# Simple pattern detection
|
||||
alert_types = [a['alert_type'] for a in alerts]
|
||||
type_counts = {}
|
||||
|
||||
for alert_type in alert_types:
|
||||
type_counts[alert_type] = type_counts.get(alert_type, 0) + 1
|
||||
|
||||
# Detect if one type dominates
|
||||
total_alerts = len(alerts)
|
||||
for alert_type, count in type_counts.items():
|
||||
if count / total_alerts > 0.6: # More than 60% of one type
|
||||
patterns.append({
|
||||
'type': 'dominant_alert_type',
|
||||
'pattern': f'{alert_type} alerts dominate ({count}/{total_alerts})',
|
||||
'confidence': 0.85
|
||||
})
|
||||
|
||||
return {'patterns': patterns, 'anomalies': anomalies}
|
||||
|
||||
|
||||
# Helper class for alert correlation
|
||||
class AlertCorrelator:
|
||||
"""
|
||||
Helper class for correlating alerts
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_correlation_strength(alert1: Dict[str, Any], alert2: Dict[str, Any]) -> float:
|
||||
"""
|
||||
Calculate correlation strength between two alerts
|
||||
"""
|
||||
# Simple correlation based on type and time proximity
|
||||
same_type = 1.0 if alert1['alert_type'] == alert2['alert_type'] else 0.3
|
||||
|
||||
time1 = datetime.fromisoformat(alert1['timestamp'])
|
||||
time2 = datetime.fromisoformat(alert2['timestamp'])
|
||||
time_diff_hours = abs((time2 - time1).total_seconds() / 3600)
|
||||
|
||||
# Time proximity score (higher for closer times)
|
||||
time_proximity = max(0, 1.0 - min(1.0, time_diff_hours / 24)) # Decays over 24 hours
|
||||
|
||||
return same_type * time_proximity
|
||||
|
||||
|
||||
# Helper class for alert prioritization
|
||||
class AlertPrioritizer:
|
||||
"""
|
||||
Helper class for prioritizing alerts
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_priority_score(alert: Dict[str, Any]) -> float:
|
||||
"""
|
||||
Calculate priority score for an alert
|
||||
"""
|
||||
# Base score from severity
|
||||
severity_scores = {'critical': 100, 'high': 75, 'medium': 50, 'low': 25}
|
||||
base_score = severity_scores.get(alert['severity'], 25)
|
||||
|
||||
# Add recency bonus (newer alerts get higher priority)
|
||||
timestamp = datetime.fromisoformat(alert['timestamp'])
|
||||
hours_old = (datetime.now() - timestamp).total_seconds() / 3600
|
||||
recency_bonus = max(0, 50 - hours_old) # Decays over 50 hours
|
||||
|
||||
return base_score + recency_bonus
|
||||
Reference in New Issue
Block a user