youtube-summarizer/backend/services/notification_service.py

295 lines
9.6 KiB
Python

"""Notification service for pipeline completion alerts."""
import asyncio
from datetime import datetime
from typing import Dict, Any, Optional, List
from enum import Enum
class NotificationType(Enum):
"""Types of notifications."""
COMPLETION = "completion"
ERROR = "error"
PROGRESS = "progress"
SYSTEM = "system"
class NotificationService:
"""Handles various types of notifications for pipeline events."""
def __init__(self):
self.enabled = True
self.notification_history: List[Dict[str, Any]] = []
async def send_completion_notification(
self,
job_id: str,
result: Dict[str, Any],
notification_config: Dict[str, Any] = None
) -> bool:
"""Send completion notification.
Args:
job_id: Pipeline job ID
result: Pipeline result data
notification_config: Notification configuration
Returns:
True if notification sent successfully
"""
if not self.enabled:
return False
try:
notification_data = {
"type": NotificationType.COMPLETION.value,
"job_id": job_id,
"timestamp": datetime.utcnow().isoformat(),
"video_title": result.get("video_metadata", {}).get("title", "Unknown Video"),
"processing_time": result.get("processing_time_seconds"),
"quality_score": result.get("quality_score"),
"summary_preview": self._get_summary_preview(result.get("summary")),
"success": True
}
# Store in history
self._add_to_history(notification_data)
# In a real implementation, this would send emails, webhooks, etc.
# For now, we'll just log the notification
await self._log_notification(notification_data)
return True
except Exception as e:
print(f"Failed to send completion notification: {e}")
return False
async def send_error_notification(
self,
job_id: str,
error: Dict[str, Any],
notification_config: Dict[str, Any] = None
) -> bool:
"""Send error notification.
Args:
job_id: Pipeline job ID
error: Error information
notification_config: Notification configuration
Returns:
True if notification sent successfully
"""
if not self.enabled:
return False
try:
notification_data = {
"type": NotificationType.ERROR.value,
"job_id": job_id,
"timestamp": datetime.utcnow().isoformat(),
"error_message": error.get("message", "Unknown error"),
"error_type": error.get("type", "UnknownError"),
"retry_count": error.get("retry_count", 0),
"stage": error.get("stage", "unknown"),
"success": False
}
# Store in history
self._add_to_history(notification_data)
await self._log_notification(notification_data)
return True
except Exception as e:
print(f"Failed to send error notification: {e}")
return False
async def send_progress_notification(
self,
job_id: str,
progress: Dict[str, Any],
notification_config: Dict[str, Any] = None
) -> bool:
"""Send progress notification (typically only for major milestones).
Args:
job_id: Pipeline job ID
progress: Progress information
notification_config: Notification configuration
Returns:
True if notification sent successfully
"""
if not self.enabled:
return False
# Only send progress notifications for major milestones
milestone_stages = ["extracting_transcript", "generating_summary", "completed"]
current_stage = progress.get("stage", "")
if current_stage not in milestone_stages:
return True # Skip non-milestone progress updates
try:
notification_data = {
"type": NotificationType.PROGRESS.value,
"job_id": job_id,
"timestamp": datetime.utcnow().isoformat(),
"stage": current_stage,
"percentage": progress.get("percentage", 0),
"message": progress.get("message", "Processing..."),
"milestone": True
}
# Store in history (but don't clutter with too many progress updates)
if current_stage in ["generating_summary", "completed"]:
self._add_to_history(notification_data)
await self._log_notification(notification_data)
return True
except Exception as e:
print(f"Failed to send progress notification: {e}")
return False
async def send_system_notification(
self,
message: str,
notification_type: str = "info",
metadata: Dict[str, Any] = None
) -> bool:
"""Send system-level notification.
Args:
message: Notification message
notification_type: Type of system notification (info, warning, error)
metadata: Additional metadata
Returns:
True if notification sent successfully
"""
if not self.enabled:
return False
try:
notification_data = {
"type": NotificationType.SYSTEM.value,
"timestamp": datetime.utcnow().isoformat(),
"message": message,
"notification_type": notification_type,
"metadata": metadata or {}
}
self._add_to_history(notification_data)
await self._log_notification(notification_data)
return True
except Exception as e:
print(f"Failed to send system notification: {e}")
return False
def _get_summary_preview(self, summary: Optional[str]) -> Optional[str]:
"""Get a preview of the summary for notifications.
Args:
summary: Full summary text
Returns:
Preview text or None
"""
if not summary:
return None
# Return first 100 characters with ellipsis
if len(summary) <= 100:
return summary
else:
return summary[:97] + "..."
def _add_to_history(self, notification_data: Dict[str, Any]):
"""Add notification to history.
Args:
notification_data: Notification data to store
"""
self.notification_history.append(notification_data)
# Keep only last 1000 notifications to prevent memory bloat
if len(self.notification_history) > 1000:
self.notification_history = self.notification_history[-1000:]
async def _log_notification(self, notification_data: Dict[str, Any]):
"""Log notification for debugging/monitoring.
Args:
notification_data: Notification data to log
"""
notification_type = notification_data.get("type", "unknown")
job_id = notification_data.get("job_id", "system")
message = notification_data.get("message", "")
print(f"[NOTIFICATION] [{notification_type.upper()}] Job {job_id}: {message}")
# In production, this would integrate with logging service
# Could also send to external services like Slack, Discord, email, etc.
def get_notification_history(
self,
limit: int = 50,
notification_type: Optional[str] = None
) -> List[Dict[str, Any]]:
"""Get notification history.
Args:
limit: Maximum number of notifications to return
notification_type: Filter by notification type
Returns:
List of notification records
"""
history = self.notification_history
# Filter by type if specified
if notification_type:
history = [
n for n in history
if n.get("type") == notification_type
]
# Return most recent first
return list(reversed(history[-limit:]))
def get_notification_stats(self) -> Dict[str, Any]:
"""Get notification statistics.
Returns:
Notification statistics
"""
total_notifications = len(self.notification_history)
type_counts = {}
for notification in self.notification_history:
notification_type = notification.get("type", "unknown")
type_counts[notification_type] = type_counts.get(notification_type, 0) + 1
return {
"total_notifications": total_notifications,
"notifications_by_type": type_counts,
"enabled": self.enabled
}
def enable_notifications(self):
"""Enable notification sending."""
self.enabled = True
def disable_notifications(self):
"""Disable notification sending."""
self.enabled = False
def clear_history(self):
"""Clear notification history."""
self.notification_history.clear()