import requests import cv2 import numpy as np from datetime import datetime from pathlib import Path import logging import base64 import io import json logger = logging.getLogger(__name__) class DiscordAlertManager: """Manages Discord webhook alerts for hygiene violations.""" def __init__(self, discord_config: dict): """ discord_config: { 'webhook_url': 'your_webhook_url' } """ self.webhook_url = discord_config['webhook_url'] self.alert_cooldown = 300 self.last_alert_time = None self.dirty_start_time = None self.dirty_threshold_seconds = 10 self.dirty_coverage_threshold = 0.06 def should_send_alert(self, dirty_coverage: float, current_time: datetime) -> bool: """Same logic as before""" if dirty_coverage < self.dirty_coverage_threshold: self.dirty_start_time = None return False if self.dirty_start_time is None: self.dirty_start_time = current_time return False dirty_duration = (current_time - self.dirty_start_time).total_seconds() if dirty_duration < self.dirty_threshold_seconds: return False if self.last_alert_time is not None: time_since_last = (current_time - self.last_alert_time).total_seconds() if time_since_last < self.alert_cooldown: return False return True def generate_heatmap_image(self, frame: np.ndarray, gmm_heatmap: np.ndarray, output_path: str) -> str: """Generate heatmap visualization""" result = frame.copy() height, width = result.shape[:2] if gmm_heatmap.shape != (height, width): heatmap_resized = cv2.resize(gmm_heatmap, (width, height)) else: heatmap_resized = gmm_heatmap heatmap_colored = cv2.applyColorMap( (heatmap_resized * 255).astype(np.uint8), cv2.COLORMAP_JET ) alpha = 0.5 result = cv2.addWeighted(frame, 1 - alpha, heatmap_colored, alpha, 0) # Add info panel avg_dirt = np.mean(heatmap_resized) max_dirt = np.max(heatmap_resized) dirty_pixels = np.sum(heatmap_resized > 0.60) coverage_percent = (dirty_pixels / heatmap_resized.size) * 100 cv2.rectangle(result, (10, 10), (400, 120), (0, 0, 0), -1) cv2.rectangle(result, (10, 10), (400, 120), (255, 255, 255), 2) info_text = [ f"Average Dirt: {avg_dirt:.2f}", f"Maximum Dirt: {max_dirt:.2f}", f"Red Zone: {coverage_percent:.1f}%", f"Time: {datetime.now().strftime('%H:%M:%S')}" ] for i, text in enumerate(info_text): cv2.putText(result, text, (20, 35 + i * 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1) cv2.imwrite(output_path, result) return output_path def send_alert(self, camera_name: str, dirty_coverage: float, dirty_duration: int, frame: np.ndarray, gmm_heatmap: np.ndarray) -> bool: """Send Discord webhook alert with embedded image""" try: # Generate image temp_image_path = f"tmp/heatmap_{datetime.now().timestamp()}.png" self.generate_heatmap_image(frame, gmm_heatmap, temp_image_path) # Calculate duration duration_mins = dirty_duration // 60 duration_secs = dirty_duration % 60 # Create rich embed embed = { "title": "🚨 CLEANING ALERT", "description": f"**{camera_name}** requires immediate attention!", "color": 15158332, # Red color (#E74C3C) "fields": [ { "name": "📍 Location", "value": camera_name, "inline": True }, { "name": "🔴 Coverage", "value": f"{dirty_coverage*100:.1f}%", "inline": True }, { "name": "⏱ Duration", "value": f"{duration_mins}m {duration_secs}s", "inline": True }, { "name": "⚠️ Action Required", "value": "Table has exceeded cleanliness threshold and needs cleaning.", "inline": False } ], "footer": { "text": "Kitchen Hygiene Monitoring System" }, "timestamp": datetime.utcnow().isoformat() } # Prepare webhook payload with embeds payload = { "username": "Hygiene Monitor Bot", "avatar_url": "https://cdn-icons-png.flaticon.com/512/3699/3699516.png", "embeds": [embed] } # Read the image file with open(temp_image_path, 'rb') as f: image_data = f.read() # Prepare the multipart form data files = { 'payload_json': (None, json.dumps(payload), 'application/json'), 'file': ('heatmap.png', image_data, 'image/png') } # Send the request response = requests.post(self.webhook_url, files=files) if response.status_code in [200, 204]: self.last_alert_time = datetime.now() logger.info(f"✅ Discord alert sent for {camera_name}") Path(temp_image_path).unlink(missing_ok=True) return True else: logger.error(f"Discord webhook error: {response.status_code} - {response.text}") return False except Exception as e: logger.error(f"Failed to send Discord alert: {str(e)}") import traceback logger.error(traceback.format_exc()) return False