218 lines
9.5 KiB
Python
218 lines
9.5 KiB
Python
# shared/alerts/templates.py
|
||
"""
|
||
Alert and recommendation templates in Spanish for the bakery platform
|
||
"""
|
||
|
||
from typing import Dict, Any
|
||
|
||
ITEM_TEMPLATES = {
|
||
# ALERTS - Critical Issues Requiring Immediate Action
|
||
'critical_stock_shortage': {
|
||
'es': {
|
||
'title': '🚨 Stock Crítico: {ingredient_name}',
|
||
'message': 'Solo {current_stock}kg disponibles, necesarios {required_stock}kg para producción de mañana. Acción inmediata requerida.',
|
||
'actions': ['Realizar pedido de emergencia', 'Contactar proveedor', 'Ajustar plan de producción']
|
||
},
|
||
'en': {
|
||
'title': '🚨 Critical Stock: {ingredient_name}',
|
||
'message': 'Only {current_stock}kg available, {required_stock}kg needed for tomorrow\'s production. Immediate action required.',
|
||
'actions': ['Place emergency order', 'Contact supplier', 'Adjust production plan']
|
||
}
|
||
},
|
||
'temperature_breach': {
|
||
'es': {
|
||
'title': '🌡️ ALERTA TEMPERATURA',
|
||
'message': '{location}: {temperature}°C durante {duration} minutos. Revisar productos inmediatamente para evitar deterioro.',
|
||
'actions': ['Verificar productos', 'Llamar técnico refrigeración', 'Documentar incidencia', 'Mover productos']
|
||
},
|
||
'en': {
|
||
'title': '🌡️ TEMPERATURE ALERT',
|
||
'message': '{location}: {temperature}°C for {duration} minutes. Check products immediately to prevent spoilage.',
|
||
'actions': ['Check products', 'Call refrigeration technician', 'Document incident', 'Move products']
|
||
}
|
||
},
|
||
'production_delay': {
|
||
'es': {
|
||
'title': '⏰ Retraso en Producción',
|
||
'message': 'Lote {batch_name} con {delay_minutes} minutos de retraso. Impacto en entregas del día.',
|
||
'actions': ['Acelerar producción', 'Notificar clientes', 'Reorganizar horarios', 'Buscar capacidad adicional']
|
||
}
|
||
},
|
||
'expired_products': {
|
||
'es': {
|
||
'title': '📅 Productos Caducados',
|
||
'message': '{product_count} productos han caducado hoy. Retirar inmediatamente por seguridad alimentaria.',
|
||
'actions': ['Retirar productos', 'Revisar inventario', 'Ajustar pedidos', 'Documentar pérdidas']
|
||
}
|
||
},
|
||
'equipment_failure': {
|
||
'es': {
|
||
'title': '⚙️ Fallo de Equipo',
|
||
'message': '{equipment_name} no está funcionando correctamente. Producción afectada.',
|
||
'actions': ['Parar producción', 'Llamar mantenimiento', 'Usar equipo alternativo', 'Documentar fallo']
|
||
}
|
||
},
|
||
'order_overload': {
|
||
'es': {
|
||
'title': '📋 Sobrecarga de Pedidos',
|
||
'message': 'Capacidad excedida en {percentage}%. Riesgo de no cumplir entregas.',
|
||
'actions': ['Priorizar pedidos', 'Aumentar turnos', 'Rechazar nuevos pedidos', 'Buscar ayuda externa']
|
||
}
|
||
},
|
||
'supplier_delay': {
|
||
'es': {
|
||
'title': '🚚 Retraso de Proveedor',
|
||
'message': 'Entrega de {supplier_name} retrasada {hours} horas. Impacto en producción de {products}.',
|
||
'actions': ['Contactar proveedor', 'Buscar alternativas', 'Ajustar producción', 'Usar stock reserva']
|
||
}
|
||
},
|
||
|
||
# RECOMMENDATIONS - Proactive Suggestions for Optimization
|
||
'inventory_optimization': {
|
||
'es': {
|
||
'title': '📈 Optimización de Stock: {ingredient_name}',
|
||
'message': 'Basado en tendencias de {period} días, sugerimos aumentar stock mínimo en {suggested_increase}kg para reducir costos.',
|
||
'actions': ['Revisar niveles mínimos', 'Analizar proveedores', 'Actualizar configuración', 'Programar pedido mayor']
|
||
},
|
||
'en': {
|
||
'title': '📈 Stock Optimization: {ingredient_name}',
|
||
'message': 'Based on {period} day trends, suggest increasing minimum stock by {suggested_increase}kg to reduce costs.',
|
||
'actions': ['Review minimum levels', 'Analyze suppliers', 'Update configuration', 'Schedule larger order']
|
||
}
|
||
},
|
||
'production_efficiency': {
|
||
'es': {
|
||
'title': '⚙️ Mejora de Eficiencia',
|
||
'message': 'Cambiar horarios de horneado a {suggested_time} puede reducir costos energéticos en {savings_percent}%.',
|
||
'actions': ['Revisar horarios', 'Consultar personal', 'Probar nuevo horario', 'Medir resultados']
|
||
}
|
||
},
|
||
'sales_opportunity': {
|
||
'es': {
|
||
'title': '💰 Oportunidad de Venta',
|
||
'message': '{product_name} tiene alta demanda los {days}. Incrementar producción puede aumentar ventas {increase_percent}%.',
|
||
'actions': ['Aumentar producción', 'Promocionar producto', 'Revisar precios', 'Planificar ingredientes']
|
||
}
|
||
},
|
||
'seasonal_adjustment': {
|
||
'es': {
|
||
'title': '🍂 Ajuste Estacional',
|
||
'message': 'Época de {season}: ajustar producción de {products} según patrones históricos.',
|
||
'actions': ['Revisar recetas estacionales', 'Ajustar inventario', 'Planificar promociones', 'Entrenar personal']
|
||
}
|
||
},
|
||
'cost_reduction': {
|
||
'es': {
|
||
'title': '💡 Reducción de Costos',
|
||
'message': 'Cambiar a proveedor {supplier_name} para {ingredient} puede ahorrar {savings_euros}€/mes.',
|
||
'actions': ['Evaluar calidad', 'Negociar precios', 'Probar muestras', 'Cambiar proveedor gradualmente']
|
||
}
|
||
},
|
||
'waste_reduction': {
|
||
'es': {
|
||
'title': '♻️ Reducción de Desperdicio',
|
||
'message': 'Ajustar tamaños de lote de {product} puede reducir desperdicio en {waste_reduction_percent}%.',
|
||
'actions': ['Analizar ventas', 'Ajustar recetas', 'Cambiar lotes', 'Monitorear resultados']
|
||
}
|
||
},
|
||
'quality_improvement': {
|
||
'es': {
|
||
'title': '⭐ Mejora de Calidad',
|
||
'message': 'Temperatura de horneado de {product} puede optimizarse para mejor textura y sabor.',
|
||
'actions': ['Probar temperaturas', 'Documentar cambios', 'Entrenar panaderos', 'Obtener feedback']
|
||
}
|
||
},
|
||
'customer_satisfaction': {
|
||
'es': {
|
||
'title': '😊 Satisfacción del Cliente',
|
||
'message': 'Clientes solicitan más {product} los {days}. Considerar aumentar disponibilidad.',
|
||
'actions': ['Revisar comentarios', 'Aumentar producción', 'Crear promociones', 'Mejorar exhibición']
|
||
}
|
||
},
|
||
'energy_optimization': {
|
||
'es': {
|
||
'title': '⚡ Optimización Energética',
|
||
'message': 'Consolidar horneado entre {start_time} y {end_time} puede reducir costos energéticos {savings_euros}€/día.',
|
||
'actions': ['Revisar horarios energía', 'Reorganizar producción', 'Optimizar hornos', 'Medir consumo']
|
||
}
|
||
},
|
||
'staff_optimization': {
|
||
'es': {
|
||
'title': '👥 Optimización de Personal',
|
||
'message': 'Picos de trabajo los {days} a las {hours}. Considerar ajustar turnos para mejor eficiencia.',
|
||
'actions': ['Analizar cargas trabajo', 'Reorganizar turnos', 'Entrenar polivalencia', 'Contratar temporal']
|
||
}
|
||
}
|
||
}
|
||
|
||
def format_item_message(template_key: str, language: str, **kwargs) -> Dict[str, Any]:
|
||
"""Format item message using template with validation"""
|
||
template = ITEM_TEMPLATES.get(template_key, {}).get(language, {})
|
||
|
||
if not template:
|
||
# Fallback for missing templates
|
||
return {
|
||
'title': f'Notificación: {template_key}',
|
||
'message': f'Información: {", ".join([f"{k}: {v}" for k, v in kwargs.items()])}',
|
||
'actions': ['Revisar', 'Documentar']
|
||
}
|
||
|
||
try:
|
||
# Format with provided kwargs, handling missing values gracefully
|
||
formatted_title = template['title'].format(**kwargs)
|
||
formatted_message = template['message'].format(**kwargs)
|
||
|
||
return {
|
||
'title': formatted_title,
|
||
'message': formatted_message,
|
||
'actions': template.get('actions', [])
|
||
}
|
||
except KeyError as e:
|
||
# Handle missing format parameters
|
||
return {
|
||
'title': template.get('title', f'Notificación: {template_key}'),
|
||
'message': f"Error en plantilla - parámetro faltante: {e}. Datos: {kwargs}",
|
||
'actions': template.get('actions', ['Revisar configuración'])
|
||
}
|
||
|
||
def get_severity_emoji(severity: str) -> str:
|
||
"""Get emoji for severity level"""
|
||
emoji_map = {
|
||
'urgent': '🚨',
|
||
'high': '⚠️',
|
||
'medium': '💡',
|
||
'low': 'ℹ️'
|
||
}
|
||
return emoji_map.get(severity, '📋')
|
||
|
||
def get_item_type_emoji(item_type: str) -> str:
|
||
"""Get emoji for item type"""
|
||
emoji_map = {
|
||
'alert': '🚨',
|
||
'recommendation': '💡'
|
||
}
|
||
return emoji_map.get(item_type, '📋')
|
||
|
||
def format_business_time(hour: int) -> str:
|
||
"""Format hour in Spanish business context"""
|
||
if hour == 0:
|
||
return "medianoche"
|
||
elif hour < 12:
|
||
return f"{hour}:00 AM"
|
||
elif hour == 12:
|
||
return "12:00 PM (mediodía)"
|
||
else:
|
||
return f"{hour-12}:00 PM"
|
||
|
||
def get_spanish_day_name(day_number: int) -> str:
|
||
"""Get Spanish day name (0=Monday)"""
|
||
days = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"]
|
||
return days[day_number] if 0 <= day_number <= 6 else "día desconocido"
|
||
|
||
def format_currency(amount: float) -> str:
|
||
"""Format currency in Spanish Euro format"""
|
||
return f"{amount:.2f}€"
|
||
|
||
def format_percentage(value: float) -> str:
|
||
"""Format percentage in Spanish format"""
|
||
return f"{value:.1f}%" |