Files
bakery-ia/shared/alerts/context_templates.py

949 lines
37 KiB
Python
Raw Normal View History

"""
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)