""" Context-Aware Alert Message Templates with i18n Support This module generates parametrized alert messages that can be translated by the frontend. Instead of hardcoded Spanish messages, we generate structured message keys and parameters. Messages are generated AFTER enrichment, leveraging: - Orchestrator context (AI actions already taken) - Business impact (financial, customers affected) - Urgency context (hours until consequence, actual dates) - User agency (supplier contacts, external dependencies) Frontend uses i18n to translate message keys with parameters. """ from datetime import datetime, timedelta from typing import Dict, Any, Optional, List from shared.schemas.alert_types import ( EnrichedAlert, OrchestratorContext, BusinessImpact, UrgencyContext, UserAgency, TrendContext, SmartAction ) import structlog logger = structlog.get_logger() def format_date_spanish(dt: datetime) -> str: """Format datetime in Spanish (for backwards compatibility)""" days = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"] months = ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"] day_name = days[dt.weekday()] month_name = months[dt.month - 1] return f"{day_name} {dt.day} de {month_name}" def format_iso_date(dt: datetime) -> str: """Format datetime as ISO date for frontend i18n""" return dt.strftime('%Y-%m-%d') def get_production_date(metadata: Dict[str, Any], default_days: int = 1) -> datetime: """Get actual production date from metadata or estimate""" if metadata.get('production_date'): if isinstance(metadata['production_date'], str): return datetime.fromisoformat(metadata['production_date']) return metadata['production_date'] else: return datetime.now() + timedelta(days=default_days) class ContextualMessageGenerator: """Generates context-aware parametrized messages for i18n""" @staticmethod def generate_message_data(enriched: EnrichedAlert) -> Dict[str, Any]: """ Generate contextual message data with i18n support Returns dict with: - title_key: i18n key for title - title_params: parameters for title translation - message_key: i18n key for message - message_params: parameters for message translation - fallback_title: fallback if i18n not available - fallback_message: fallback if i18n not available """ alert_type = enriched.alert_type # Dispatch to specific generator based on alert type generators = { # Inventory alerts 'critical_stock_shortage': ContextualMessageGenerator._stock_shortage, 'low_stock_warning': ContextualMessageGenerator._low_stock, 'stock_depleted_by_order': ContextualMessageGenerator._stock_depleted, 'production_ingredient_shortage': ContextualMessageGenerator._ingredient_shortage, 'expired_products': ContextualMessageGenerator._expired_products, # Production alerts 'production_delay': ContextualMessageGenerator._production_delay, 'equipment_failure': ContextualMessageGenerator._equipment_failure, 'maintenance_required': ContextualMessageGenerator._maintenance_required, 'low_equipment_efficiency': ContextualMessageGenerator._low_efficiency, 'order_overload': ContextualMessageGenerator._order_overload, # Supplier alerts 'supplier_delay': ContextualMessageGenerator._supplier_delay, # Procurement alerts 'po_approval_needed': ContextualMessageGenerator._po_approval_needed, 'production_batch_start': ContextualMessageGenerator._production_batch_start, # Environmental alerts 'temperature_breach': ContextualMessageGenerator._temperature_breach, # Forecasting alerts 'demand_surge_weekend': ContextualMessageGenerator._demand_surge, 'weather_impact_alert': ContextualMessageGenerator._weather_impact, 'holiday_preparation': ContextualMessageGenerator._holiday_prep, 'severe_weather_impact': ContextualMessageGenerator._severe_weather, 'unexpected_demand_spike': ContextualMessageGenerator._demand_spike, 'demand_pattern_optimization': ContextualMessageGenerator._demand_pattern, # Recommendations 'inventory_optimization': ContextualMessageGenerator._inventory_optimization, 'production_efficiency': ContextualMessageGenerator._production_efficiency, 'sales_opportunity': ContextualMessageGenerator._sales_opportunity, 'seasonal_adjustment': ContextualMessageGenerator._seasonal_adjustment, 'cost_reduction': ContextualMessageGenerator._cost_reduction, 'waste_reduction': ContextualMessageGenerator._waste_reduction, 'quality_improvement': ContextualMessageGenerator._quality_improvement, 'customer_satisfaction': ContextualMessageGenerator._customer_satisfaction, 'energy_optimization': ContextualMessageGenerator._energy_optimization, 'staff_optimization': ContextualMessageGenerator._staff_optimization, } generator_func = generators.get(alert_type) if generator_func: return generator_func(enriched) else: # Fallback for unknown alert types return { 'title_key': f'alerts.{alert_type}.title', 'title_params': {}, 'message_key': f'alerts.{alert_type}.message', 'message_params': {'alert_type': alert_type}, 'fallback_title': f"Alerta: {alert_type}", 'fallback_message': f"Se detectó una situación que requiere atención: {alert_type}" } # =================================================================== # INVENTORY ALERTS # =================================================================== @staticmethod def _stock_shortage(enriched: EnrichedAlert) -> Dict[str, Any]: """Critical stock shortage with AI context""" metadata = enriched.alert_metadata orch = enriched.orchestrator_context urgency = enriched.urgency_context agency = enriched.user_agency ingredient_name = metadata.get('ingredient_name', 'Ingrediente') current_stock = round(metadata.get('current_stock', 0), 1) required_stock = round(metadata.get('required_stock', metadata.get('tomorrow_needed', 0)), 1) # Base parameters params = { 'ingredient_name': ingredient_name, 'current_stock': current_stock, 'required_stock': required_stock } # Determine message variant based on context if orch and orch.already_addressed and orch.action_type == "purchase_order": # AI already created PO params['po_id'] = orch.action_id params['po_amount'] = metadata.get('po_amount', 0) if orch.delivery_date: params['delivery_date'] = format_iso_date(orch.delivery_date) params['delivery_day_name'] = format_date_spanish(orch.delivery_date) if orch.action_status == "pending_approval": message_key = 'alerts.critical_stock_shortage.message_with_po_pending' else: message_key = 'alerts.critical_stock_shortage.message_with_po_created' elif urgency and urgency.time_until_consequence_hours: # Time-specific message hours = urgency.time_until_consequence_hours params['hours_until'] = round(hours, 1) message_key = 'alerts.critical_stock_shortage.message_with_hours' elif metadata.get('production_date'): # Date-specific message prod_date = get_production_date(metadata) params['production_date'] = format_iso_date(prod_date) params['production_day_name'] = format_date_spanish(prod_date) message_key = 'alerts.critical_stock_shortage.message_with_date' else: # Generic message message_key = 'alerts.critical_stock_shortage.message_generic' # Add supplier contact if available if agency and agency.requires_external_party and agency.external_party_contact: params['supplier_name'] = agency.external_party_name params['supplier_contact'] = agency.external_party_contact return { 'title_key': 'alerts.critical_stock_shortage.title', 'title_params': {'ingredient_name': ingredient_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"🚨 Stock Crítico: {ingredient_name}", 'fallback_message': f"Solo {current_stock}kg de {ingredient_name} disponibles (necesitas {required_stock}kg)." } @staticmethod def _low_stock(enriched: EnrichedAlert) -> Dict[str, Any]: """Low stock warning""" metadata = enriched.alert_metadata orch = enriched.orchestrator_context ingredient_name = metadata.get('ingredient_name', 'Ingrediente') current_stock = round(metadata.get('current_stock', 0), 1) minimum_stock = round(metadata.get('minimum_stock', 0), 1) params = { 'ingredient_name': ingredient_name, 'current_stock': current_stock, 'minimum_stock': minimum_stock } if orch and orch.already_addressed and orch.action_type == "purchase_order": params['po_id'] = orch.action_id message_key = 'alerts.low_stock.message_with_po' else: message_key = 'alerts.low_stock.message_generic' return { 'title_key': 'alerts.low_stock.title', 'title_params': {'ingredient_name': ingredient_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"⚠️ Stock Bajo: {ingredient_name}", 'fallback_message': f"Stock de {ingredient_name}: {current_stock}kg (mínimo: {minimum_stock}kg)." } @staticmethod def _stock_depleted(enriched: EnrichedAlert) -> Dict[str, Any]: """Stock depleted by order""" metadata = enriched.alert_metadata agency = enriched.user_agency ingredient_name = metadata.get('ingredient_name', 'Ingrediente') order_id = metadata.get('order_id', '???') current_stock = round(metadata.get('current_stock', 0), 1) minimum_stock = round(metadata.get('minimum_stock', 0), 1) params = { 'ingredient_name': ingredient_name, 'order_id': order_id, 'current_stock': current_stock, 'minimum_stock': minimum_stock } if agency and agency.requires_external_party and agency.external_party_contact: params['supplier_name'] = agency.external_party_name params['supplier_contact'] = agency.external_party_contact message_key = 'alerts.stock_depleted.message_with_supplier' else: message_key = 'alerts.stock_depleted.message_generic' return { 'title_key': 'alerts.stock_depleted.title', 'title_params': {'ingredient_name': ingredient_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"⚠️ Stock Agotado por Pedido: {ingredient_name}", 'fallback_message': f"El pedido #{order_id} agotaría el stock de {ingredient_name}." } @staticmethod def _ingredient_shortage(enriched: EnrichedAlert) -> Dict[str, Any]: """Production ingredient shortage""" metadata = enriched.alert_metadata impact = enriched.business_impact ingredient_name = metadata.get('ingredient_name', 'Ingrediente') shortage_amount = round(metadata.get('shortage_amount', 0), 1) affected_batches = metadata.get('affected_batches_count', 0) params = { 'ingredient_name': ingredient_name, 'shortage_amount': shortage_amount, 'affected_batches': affected_batches } if impact and impact.affected_customers: params['customer_count'] = len(impact.affected_customers) params['customer_names'] = ', '.join(impact.affected_customers[:2]) if len(impact.affected_customers) > 2: params['additional_customers'] = len(impact.affected_customers) - 2 message_key = 'alerts.ingredient_shortage.message_with_customers' else: message_key = 'alerts.ingredient_shortage.message_generic' return { 'title_key': 'alerts.ingredient_shortage.title', 'title_params': {'ingredient_name': ingredient_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"🚨 Escasez en Producción: {ingredient_name}", 'fallback_message': f"Faltan {shortage_amount}kg de {ingredient_name}." } @staticmethod def _expired_products(enriched: EnrichedAlert) -> Dict[str, Any]: """Expired products alert""" metadata = enriched.alert_metadata product_count = metadata.get('product_count', 0) expired_items = metadata.get('expired_items', []) params = { 'product_count': product_count } if len(expired_items) > 0: params['product_names'] = ', '.join([item.get('name', 'Producto') for item in expired_items[:2]]) if len(expired_items) > 2: params['additional_count'] = len(expired_items) - 2 message_key = 'alerts.expired_products.message_with_names' else: message_key = 'alerts.expired_products.message_generic' return { 'title_key': 'alerts.expired_products.title', 'title_params': {}, 'message_key': message_key, 'message_params': params, 'fallback_title': "📅 Productos Caducados", 'fallback_message': f"{product_count} producto(s) caducado(s). Retirar inmediatamente." } # =================================================================== # PRODUCTION ALERTS # =================================================================== @staticmethod def _production_delay(enriched: EnrichedAlert) -> Dict[str, Any]: """Production delay with affected orders""" metadata = enriched.alert_metadata impact = enriched.business_impact batch_name = metadata.get('batch_name', 'Lote') delay_minutes = metadata.get('delay_minutes', 0) params = { 'batch_name': batch_name, 'delay_minutes': delay_minutes } if impact and impact.affected_customers: params['customer_names'] = ', '.join(impact.affected_customers[:2]) if len(impact.affected_customers) > 2: params['additional_count'] = len(impact.affected_customers) - 2 message_key = 'alerts.production_delay.message_with_customers' elif impact and impact.affected_orders: params['affected_orders'] = impact.affected_orders message_key = 'alerts.production_delay.message_with_orders' else: message_key = 'alerts.production_delay.message_generic' return { 'title_key': 'alerts.production_delay.title', 'title_params': {}, 'message_key': message_key, 'message_params': params, 'fallback_title': "⏰ Retraso en Producción", 'fallback_message': f"Lote {batch_name} con {delay_minutes} minutos de retraso." } @staticmethod def _equipment_failure(enriched: EnrichedAlert) -> Dict[str, Any]: """Equipment failure""" metadata = enriched.alert_metadata impact = enriched.business_impact equipment_name = metadata.get('equipment_name', 'Equipo') params = { 'equipment_name': equipment_name } if impact and impact.production_batches_at_risk: params['batch_count'] = len(impact.production_batches_at_risk) message_key = 'alerts.equipment_failure.message_with_batches' else: message_key = 'alerts.equipment_failure.message_generic' return { 'title_key': 'alerts.equipment_failure.title', 'title_params': {'equipment_name': equipment_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"⚙️ Fallo de Equipo: {equipment_name}", 'fallback_message': f"{equipment_name} no está funcionando correctamente." } @staticmethod def _maintenance_required(enriched: EnrichedAlert) -> Dict[str, Any]: """Maintenance required""" metadata = enriched.alert_metadata urgency = enriched.urgency_context equipment_name = metadata.get('equipment_name', 'Equipo') params = { 'equipment_name': equipment_name } if urgency and urgency.time_until_consequence_hours: params['hours_until'] = round(urgency.time_until_consequence_hours, 1) message_key = 'alerts.maintenance_required.message_with_hours' else: params['days_until'] = metadata.get('days_until_maintenance', 0) message_key = 'alerts.maintenance_required.message_with_days' return { 'title_key': 'alerts.maintenance_required.title', 'title_params': {'equipment_name': equipment_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"🔧 Mantenimiento Requerido: {equipment_name}", 'fallback_message': f"Equipo {equipment_name} requiere mantenimiento." } @staticmethod def _low_efficiency(enriched: EnrichedAlert) -> Dict[str, Any]: """Low equipment efficiency""" metadata = enriched.alert_metadata equipment_name = metadata.get('equipment_name', 'Equipo') efficiency = round(metadata.get('efficiency_percent', 0), 1) return { 'title_key': 'alerts.low_efficiency.title', 'title_params': {'equipment_name': equipment_name}, 'message_key': 'alerts.low_efficiency.message', 'message_params': { 'equipment_name': equipment_name, 'efficiency_percent': efficiency }, 'fallback_title': f"📉 Baja Eficiencia: {equipment_name}", 'fallback_message': f"Eficiencia del {equipment_name} bajó a {efficiency}%." } @staticmethod def _order_overload(enriched: EnrichedAlert) -> Dict[str, Any]: """Order capacity overload""" metadata = enriched.alert_metadata impact = enriched.business_impact percentage = round(metadata.get('percentage', 0), 1) params = { 'percentage': percentage } if impact and impact.affected_orders: params['affected_orders'] = impact.affected_orders message_key = 'alerts.order_overload.message_with_orders' else: message_key = 'alerts.order_overload.message_generic' return { 'title_key': 'alerts.order_overload.title', 'title_params': {}, 'message_key': message_key, 'message_params': params, 'fallback_title': "📋 Sobrecarga de Pedidos", 'fallback_message': f"Capacidad excedida en {percentage}%." } # =================================================================== # SUPPLIER ALERTS # =================================================================== @staticmethod def _supplier_delay(enriched: EnrichedAlert) -> Dict[str, Any]: """Supplier delivery delay""" metadata = enriched.alert_metadata impact = enriched.business_impact agency = enriched.user_agency supplier_name = metadata.get('supplier_name', 'Proveedor') hours = round(metadata.get('hours', metadata.get('delay_hours', 0)), 0) products = metadata.get('products', metadata.get('affected_products', '')) params = { 'supplier_name': supplier_name, 'hours': hours, 'products': products } if impact and impact.production_batches_at_risk: params['batch_count'] = len(impact.production_batches_at_risk) if agency and agency.external_party_contact: params['supplier_contact'] = agency.external_party_contact message_key = 'alerts.supplier_delay.message' return { 'title_key': 'alerts.supplier_delay.title', 'title_params': {'supplier_name': supplier_name}, 'message_key': message_key, 'message_params': params, 'fallback_title': f"🚚 Retraso de Proveedor: {supplier_name}", 'fallback_message': f"Entrega de {supplier_name} retrasada {hours} hora(s)." } # =================================================================== # PROCUREMENT ALERTS # =================================================================== @staticmethod def _po_approval_needed(enriched: EnrichedAlert) -> Dict[str, Any]: """Purchase order approval needed""" metadata = enriched.alert_metadata po_number = metadata.get('po_number', 'PO-XXXX') supplier_name = metadata.get('supplier_name', 'Proveedor') total_amount = metadata.get('total_amount', 0) currency = metadata.get('currency', '€') required_delivery_date = metadata.get('required_delivery_date') # Format required delivery date for i18n required_delivery_date_iso = None if required_delivery_date: if isinstance(required_delivery_date, str): try: dt = datetime.fromisoformat(required_delivery_date.replace('Z', '+00:00')) required_delivery_date_iso = format_iso_date(dt) except: required_delivery_date_iso = required_delivery_date elif isinstance(required_delivery_date, datetime): required_delivery_date_iso = format_iso_date(required_delivery_date) params = { 'po_number': po_number, 'supplier_name': supplier_name, 'total_amount': round(total_amount, 2), 'currency': currency, 'required_delivery_date': required_delivery_date_iso or 'fecha no especificada' } return { 'title_key': 'alerts.po_approval_needed.title', 'title_params': {'po_number': po_number}, 'message_key': 'alerts.po_approval_needed.message', 'message_params': params } @staticmethod def _production_batch_start(enriched: EnrichedAlert) -> Dict[str, Any]: """Production batch ready to start""" metadata = enriched.alert_metadata batch_number = metadata.get('batch_number', 'BATCH-XXXX') product_name = metadata.get('product_name', 'Producto') quantity_planned = metadata.get('quantity_planned', 0) unit = metadata.get('unit', 'kg') priority = metadata.get('priority', 'normal') params = { 'batch_number': batch_number, 'product_name': product_name, 'quantity_planned': round(quantity_planned, 1), 'unit': unit, 'priority': priority } return { 'title_key': 'alerts.production_batch_start.title', 'title_params': {'product_name': product_name}, 'message_key': 'alerts.production_batch_start.message', 'message_params': params } # =================================================================== # ENVIRONMENTAL ALERTS # =================================================================== @staticmethod def _temperature_breach(enriched: EnrichedAlert) -> Dict[str, Any]: """Temperature breach alert""" metadata = enriched.alert_metadata location = metadata.get('location', 'Ubicación') temperature = round(metadata.get('temperature', 0), 1) duration = metadata.get('duration', 0) return { 'title_key': 'alerts.temperature_breach.title', 'title_params': {'location': location}, 'message_key': 'alerts.temperature_breach.message', 'message_params': { 'location': location, 'temperature': temperature, 'duration': duration } } # =================================================================== # FORECASTING ALERTS # =================================================================== @staticmethod def _demand_surge(enriched: EnrichedAlert) -> Dict[str, Any]: """Weekend demand surge""" metadata = enriched.alert_metadata urgency = enriched.urgency_context product_name = metadata.get('product_name', 'Producto') percentage = round(metadata.get('percentage', metadata.get('growth_percentage', 0)), 0) predicted_demand = metadata.get('predicted_demand', 0) current_stock = metadata.get('current_stock', 0) params = { 'product_name': product_name, 'percentage': percentage } if predicted_demand and current_stock: params['predicted_demand'] = round(predicted_demand, 0) params['current_stock'] = round(current_stock, 0) if urgency and urgency.time_until_consequence_hours: params['hours_until'] = round(urgency.time_until_consequence_hours, 1) return { 'title_key': 'alerts.demand_surge.title', 'title_params': {'product_name': product_name}, 'message_key': 'alerts.demand_surge.message', 'message_params': params } @staticmethod def _weather_impact(enriched: EnrichedAlert) -> Dict[str, Any]: """Weather impact on demand""" metadata = enriched.alert_metadata weather_type = metadata.get('weather_type', 'Lluvia') impact_percentage = round(metadata.get('impact_percentage', -20), 0) return { 'title_key': 'alerts.weather_impact.title', 'title_params': {}, 'message_key': 'alerts.weather_impact.message', 'message_params': { 'weather_type': weather_type, 'impact_percentage': abs(impact_percentage), 'is_negative': impact_percentage < 0 } } @staticmethod def _holiday_prep(enriched: EnrichedAlert) -> Dict[str, Any]: """Holiday preparation""" metadata = enriched.alert_metadata holiday_name = metadata.get('holiday_name', 'Festividad') days = metadata.get('days', 0) percentage = round(metadata.get('percentage', metadata.get('increase_percentage', 0)), 0) return { 'title_key': 'alerts.holiday_prep.title', 'title_params': {'holiday_name': holiday_name}, 'message_key': 'alerts.holiday_prep.message', 'message_params': { 'holiday_name': holiday_name, 'days': days, 'percentage': percentage } } @staticmethod def _severe_weather(enriched: EnrichedAlert) -> Dict[str, Any]: """Severe weather impact""" metadata = enriched.alert_metadata weather_type = metadata.get('weather_type', 'Tormenta') duration_hours = metadata.get('duration_hours', 0) return { 'title_key': 'alerts.severe_weather.title', 'title_params': {'weather_type': weather_type}, 'message_key': 'alerts.severe_weather.message', 'message_params': { 'weather_type': weather_type, 'duration_hours': duration_hours } } @staticmethod def _demand_spike(enriched: EnrichedAlert) -> Dict[str, Any]: """Unexpected demand spike""" metadata = enriched.alert_metadata trend = enriched.trend_context product_name = metadata.get('product_name', 'Producto') spike_percentage = round(metadata.get('spike_percentage', metadata.get('growth_percentage', 0)), 0) params = { 'product_name': product_name, 'spike_percentage': spike_percentage } if trend: params['current_value'] = round(trend.current_value, 0) params['baseline_value'] = round(trend.baseline_value, 0) return { 'title_key': 'alerts.demand_spike.title', 'title_params': {'product_name': product_name}, 'message_key': 'alerts.demand_spike.message', 'message_params': params } @staticmethod def _demand_pattern(enriched: EnrichedAlert) -> Dict[str, Any]: """Demand pattern optimization""" metadata = enriched.alert_metadata trend = enriched.trend_context product_name = metadata.get('product_name', 'Producto') variation = round(metadata.get('variation_percent', 0), 0) params = { 'product_name': product_name, 'variation_percent': variation } if trend and trend.possible_causes: params['possible_causes'] = ', '.join(trend.possible_causes[:2]) return { 'title_key': 'alerts.demand_pattern.title', 'title_params': {'product_name': product_name}, 'message_key': 'alerts.demand_pattern.message', 'message_params': params } # =================================================================== # RECOMMENDATIONS # =================================================================== @staticmethod def _inventory_optimization(enriched: EnrichedAlert) -> Dict[str, Any]: """Inventory optimization recommendation""" metadata = enriched.alert_metadata ingredient_name = metadata.get('ingredient_name', 'Ingrediente') period = metadata.get('period', 7) suggested_increase = round(metadata.get('suggested_increase', 0), 1) return { 'title_key': 'recommendations.inventory_optimization.title', 'title_params': {'ingredient_name': ingredient_name}, 'message_key': 'recommendations.inventory_optimization.message', 'message_params': { 'ingredient_name': ingredient_name, 'period': period, 'suggested_increase': suggested_increase } } @staticmethod def _production_efficiency(enriched: EnrichedAlert) -> Dict[str, Any]: """Production efficiency recommendation""" metadata = enriched.alert_metadata suggested_time = metadata.get('suggested_time', '') savings_percent = round(metadata.get('savings_percent', 0), 1) return { 'title_key': 'recommendations.production_efficiency.title', 'title_params': {}, 'message_key': 'recommendations.production_efficiency.message', 'message_params': { 'suggested_time': suggested_time, 'savings_percent': savings_percent } } @staticmethod def _sales_opportunity(enriched: EnrichedAlert) -> Dict[str, Any]: """Sales opportunity recommendation""" metadata = enriched.alert_metadata product_name = metadata.get('product_name', 'Producto') days = metadata.get('days', '') increase_percent = round(metadata.get('increase_percent', 0), 0) return { 'title_key': 'recommendations.sales_opportunity.title', 'title_params': {'product_name': product_name}, 'message_key': 'recommendations.sales_opportunity.message', 'message_params': { 'product_name': product_name, 'days': days, 'increase_percent': increase_percent } } @staticmethod def _seasonal_adjustment(enriched: EnrichedAlert) -> Dict[str, Any]: """Seasonal adjustment recommendation""" metadata = enriched.alert_metadata season = metadata.get('season', 'temporada') products = metadata.get('products', 'productos estacionales') return { 'title_key': 'recommendations.seasonal_adjustment.title', 'title_params': {}, 'message_key': 'recommendations.seasonal_adjustment.message', 'message_params': { 'season': season, 'products': products } } @staticmethod def _cost_reduction(enriched: EnrichedAlert) -> Dict[str, Any]: """Cost reduction recommendation""" metadata = enriched.alert_metadata supplier_name = metadata.get('supplier_name', 'Proveedor') ingredient = metadata.get('ingredient', 'ingrediente') savings_euros = round(metadata.get('savings_euros', 0), 0) return { 'title_key': 'recommendations.cost_reduction.title', 'title_params': {}, 'message_key': 'recommendations.cost_reduction.message', 'message_params': { 'supplier_name': supplier_name, 'ingredient': ingredient, 'savings_euros': savings_euros } } @staticmethod def _waste_reduction(enriched: EnrichedAlert) -> Dict[str, Any]: """Waste reduction recommendation""" metadata = enriched.alert_metadata product = metadata.get('product', 'producto') waste_reduction_percent = round(metadata.get('waste_reduction_percent', 0), 0) return { 'title_key': 'recommendations.waste_reduction.title', 'title_params': {}, 'message_key': 'recommendations.waste_reduction.message', 'message_params': { 'product': product, 'waste_reduction_percent': waste_reduction_percent } } @staticmethod def _quality_improvement(enriched: EnrichedAlert) -> Dict[str, Any]: """Quality improvement recommendation""" metadata = enriched.alert_metadata product = metadata.get('product', 'producto') return { 'title_key': 'recommendations.quality_improvement.title', 'title_params': {}, 'message_key': 'recommendations.quality_improvement.message', 'message_params': { 'product': product } } @staticmethod def _customer_satisfaction(enriched: EnrichedAlert) -> Dict[str, Any]: """Customer satisfaction recommendation""" metadata = enriched.alert_metadata product = metadata.get('product', 'producto') days = metadata.get('days', '') return { 'title_key': 'recommendations.customer_satisfaction.title', 'title_params': {}, 'message_key': 'recommendations.customer_satisfaction.message', 'message_params': { 'product': product, 'days': days } } @staticmethod def _energy_optimization(enriched: EnrichedAlert) -> Dict[str, Any]: """Energy optimization recommendation""" metadata = enriched.alert_metadata start_time = metadata.get('start_time', '') end_time = metadata.get('end_time', '') savings_euros = round(metadata.get('savings_euros', 0), 0) return { 'title_key': 'recommendations.energy_optimization.title', 'title_params': {}, 'message_key': 'recommendations.energy_optimization.message', 'message_params': { 'start_time': start_time, 'end_time': end_time, 'savings_euros': savings_euros } } @staticmethod def _staff_optimization(enriched: EnrichedAlert) -> Dict[str, Any]: """Staff optimization recommendation""" metadata = enriched.alert_metadata days = metadata.get('days', '') hours = metadata.get('hours', '') return { 'title_key': 'recommendations.staff_optimization.title', 'title_params': {}, 'message_key': 'recommendations.staff_optimization.message', 'message_params': { 'days': days, 'hours': hours } } def generate_contextual_message(enriched: EnrichedAlert) -> Dict[str, Any]: """ Main entry point for contextual message generation with i18n support Args: enriched: Fully enriched alert with all context Returns: Dict with: - title_key: i18n translation key for title - title_params: parameters for title translation - message_key: i18n translation key for message - message_params: parameters for message translation - fallback_title: fallback if i18n not available - fallback_message: fallback if i18n not available """ return ContextualMessageGenerator.generate_message_data(enriched)