New alert system and panel de control page

This commit is contained in:
Urtzi Alfaro
2025-11-27 15:52:40 +01:00
parent 1a2f4602f3
commit e902419b6e
178 changed files with 20982 additions and 6944 deletions

View File

@@ -277,9 +277,34 @@ class BaseAlertService:
# Publishing (Updated for type)
async def publish_item(self, tenant_id: UUID, item: Dict[str, Any], item_type: str = 'alert'):
"""Publish alert or recommendation to RabbitMQ with deduplication"""
"""Publish alert or recommendation to RabbitMQ with deduplication and validation"""
try:
# Validate alert structure before publishing
from shared.schemas.alert_types import RawAlert
try:
raw_alert = RawAlert(
tenant_id=str(tenant_id),
alert_type=item.get('type'),
title=item.get('title'),
message=item.get('message'),
service=self.config.SERVICE_NAME,
actions=item.get('actions', []),
alert_metadata=item.get('metadata', {}),
item_type=item_type
)
# Validation passed, continue with validated data
logger.debug("Alert schema validation passed",
service=self.config.SERVICE_NAME,
alert_type=item.get('type'))
except Exception as validation_error:
logger.error("Alert schema validation failed",
service=self.config.SERVICE_NAME,
alert_type=item.get('type'),
error=str(validation_error))
self._errors_count += 1
return False
# Generate proper deduplication key based on alert type and specific identifiers
unique_id = self._generate_unique_identifier(item)
item_key = f"{tenant_id}:{item_type}:{item['type']}:{unique_id}"
@@ -350,6 +375,7 @@ class BaseAlertService:
metadata = item.get('metadata', {})
# Generate unique identifier based on alert type
# Inventory alerts
if alert_type == 'overstock_warning':
return metadata.get('ingredient_id', '')
elif alert_type == 'critical_stock_shortage' or alert_type == 'low_stock_warning':
@@ -377,6 +403,47 @@ class BaseAlertService:
return f"opt:{metadata.get('ingredient_id', '')}:{metadata.get('recommendation_type', '')}"
elif alert_type == 'waste_reduction':
return f"waste:{metadata.get('ingredient_id', '')}"
# Procurement alerts
elif alert_type == 'procurement_pos_pending_approval':
# Use hash of PO IDs for grouped alerts
pos = metadata.get('pos', [])
if pos:
po_ids = sorted([str(po.get('po_id', '')) for po in pos])
import hashlib
return hashlib.md5(':'.join(po_ids).encode()).hexdigest()[:16]
return ''
elif alert_type == 'procurement_approval_reminder':
return metadata.get('po_id', '')
elif alert_type == 'procurement_critical_po':
return metadata.get('po_id', '')
elif alert_type == 'procurement_po_approved':
return metadata.get('po_id', '')
elif alert_type == 'procurement_auto_approval_summary':
# Daily summary - use date as identifier
summary_date = metadata.get('summary_date', '')[:10] # Date only
return f"summary:{summary_date}"
# Production alerts
elif alert_type in ['severe_capacity_overload', 'capacity_overload', 'near_capacity']:
return f"capacity:{metadata.get('planned_date', '')}"
elif alert_type == 'production_delay':
return metadata.get('batch_id', '')
elif alert_type == 'quality_control_failure':
return metadata.get('quality_check_id', '')
elif alert_type in ['equipment_failure', 'maintenance_required', 'low_equipment_efficiency']:
return metadata.get('equipment_id', '')
elif alert_type == 'production_ingredient_shortage':
return metadata.get('ingredient_id', '')
# Forecasting alerts
elif alert_type in ['demand_surge_weekend', 'holiday_preparation', 'demand_spike_detected', 'unexpected_demand_spike']:
return f"{alert_type}:{metadata.get('product_name', '')}:{metadata.get('forecast_date', '')}"
elif alert_type == 'weather_impact_alert':
return f"weather:{metadata.get('forecast_date', '')}"
elif alert_type == 'severe_weather_impact':
return f"severe_weather:{metadata.get('weather_type', '')}:{metadata.get('duration_hours', '')}"
else:
# Fallback to generic metadata.id or empty string
return metadata.get('id', '')
@@ -485,25 +552,20 @@ class BaseAlertService:
class AlertServiceMixin:
"""Mixin providing common alert helper methods"""
def format_spanish_message(self, template_key: str, **kwargs) -> Dict[str, Any]:
"""Format Spanish alert message"""
from shared.alerts.templates import format_item_message
return format_item_message(template_key, 'es', **kwargs)
def get_business_hours_severity(self, base_severity: str) -> str:
"""Adjust severity based on business hours"""
current_hour = datetime.now().hour
# Reduce non-critical severity outside business hours (7-20)
if not (7 <= current_hour <= 20):
if base_severity == 'medium':
return 'low'
elif base_severity == 'high' and current_hour < 6 or current_hour > 22:
return 'medium'
return base_severity
def should_send_recommendation(self, tenant_id: UUID, rec_type: str) -> bool:
"""Check if recommendation should be sent based on tenant preferences"""
# Implement tenant-specific recommendation frequency limits