Initial commit - production deployment
This commit is contained in:
244
services/alert_processor/app/enrichment/message_generator.py
Normal file
244
services/alert_processor/app/enrichment/message_generator.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""
|
||||
Message generator for creating i18n message keys and parameters.
|
||||
|
||||
Converts minimal event metadata into structured i18n format for frontend translation.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
from app.utils.message_templates import ALERT_TEMPLATES, NOTIFICATION_TEMPLATES, RECOMMENDATION_TEMPLATES
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class MessageGenerator:
|
||||
"""Generates i18n message keys and parameters from event metadata"""
|
||||
|
||||
def generate_message(self, event_type: str, metadata: Dict[str, Any], event_class: str = "alert") -> dict:
|
||||
"""
|
||||
Generate i18n structure for frontend.
|
||||
|
||||
Args:
|
||||
event_type: Alert/notification/recommendation type
|
||||
metadata: Event metadata dictionary
|
||||
event_class: One of: alert, notification, recommendation
|
||||
|
||||
Returns:
|
||||
Dictionary with title_key, title_params, message_key, message_params
|
||||
"""
|
||||
|
||||
# Select appropriate template collection
|
||||
if event_class == "notification":
|
||||
templates = NOTIFICATION_TEMPLATES
|
||||
elif event_class == "recommendation":
|
||||
templates = RECOMMENDATION_TEMPLATES
|
||||
else:
|
||||
templates = ALERT_TEMPLATES
|
||||
|
||||
template = templates.get(event_type)
|
||||
|
||||
if not template:
|
||||
logger.warning("no_template_found", event_type=event_type, event_class=event_class)
|
||||
return self._generate_fallback(event_type, metadata)
|
||||
|
||||
# Build parameters from metadata
|
||||
title_params = self._build_params(template["title_params"], metadata)
|
||||
message_params = self._build_params(template["message_params"], metadata)
|
||||
|
||||
# Select message variant based on context
|
||||
message_key = self._select_message_variant(
|
||||
template["message_variants"],
|
||||
metadata
|
||||
)
|
||||
|
||||
return {
|
||||
"title_key": template["title_key"],
|
||||
"title_params": title_params,
|
||||
"message_key": message_key,
|
||||
"message_params": message_params
|
||||
}
|
||||
|
||||
def _generate_fallback(self, event_type: str, metadata: Dict[str, Any]) -> dict:
|
||||
"""Generate fallback message structure when template not found"""
|
||||
return {
|
||||
"title_key": "alerts.generic.title",
|
||||
"title_params": {},
|
||||
"message_key": "alerts.generic.message",
|
||||
"message_params": {
|
||||
"event_type": event_type,
|
||||
"metadata_summary": self._summarize_metadata(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
def _summarize_metadata(self, metadata: Dict[str, Any]) -> str:
|
||||
"""Create human-readable summary of metadata"""
|
||||
# Take first 3 fields
|
||||
items = list(metadata.items())[:3]
|
||||
summary_parts = [f"{k}: {v}" for k, v in items]
|
||||
return ", ".join(summary_parts)
|
||||
|
||||
def _build_params(self, param_mapping: dict, metadata: dict) -> dict:
|
||||
"""
|
||||
Extract and transform parameters from metadata.
|
||||
|
||||
param_mapping format: {"display_param_name": "metadata_key"}
|
||||
"""
|
||||
params = {}
|
||||
|
||||
for param_key, metadata_key in param_mapping.items():
|
||||
if metadata_key in metadata:
|
||||
value = metadata[metadata_key]
|
||||
|
||||
# Apply transformations based on parameter suffix
|
||||
if param_key.endswith("_kg"):
|
||||
value = round(float(value), 1)
|
||||
elif param_key.endswith("_eur"):
|
||||
value = round(float(value), 2)
|
||||
elif param_key.endswith("_percentage"):
|
||||
value = round(float(value), 1)
|
||||
elif param_key.endswith("_date"):
|
||||
value = self._format_date(value)
|
||||
elif param_key.endswith("_day_name"):
|
||||
value = self._format_day_name(value)
|
||||
elif param_key.endswith("_datetime"):
|
||||
value = self._format_datetime(value)
|
||||
|
||||
params[param_key] = value
|
||||
|
||||
return params
|
||||
|
||||
def _select_message_variant(self, variants: dict, metadata: dict) -> str:
|
||||
"""
|
||||
Select appropriate message variant based on metadata context.
|
||||
|
||||
Checks for specific conditions in priority order.
|
||||
"""
|
||||
|
||||
# Check for PO-related variants
|
||||
if "po_id" in metadata:
|
||||
if metadata.get("po_status") == "pending_approval":
|
||||
variant = variants.get("with_po_pending")
|
||||
if variant:
|
||||
return variant
|
||||
else:
|
||||
variant = variants.get("with_po_created")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for time-based variants
|
||||
if "hours_until" in metadata:
|
||||
variant = variants.get("with_hours")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
if "production_date" in metadata or "planned_date" in metadata:
|
||||
variant = variants.get("with_date")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for customer-related variants
|
||||
if "customer_names" in metadata and metadata.get("customer_names"):
|
||||
variant = variants.get("with_customers")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for order-related variants
|
||||
if "affected_orders" in metadata and metadata.get("affected_orders", 0) > 0:
|
||||
variant = variants.get("with_orders")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for supplier contact variants
|
||||
if "supplier_contact" in metadata:
|
||||
variant = variants.get("with_supplier")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for batch-related variants
|
||||
if "affected_batches" in metadata and metadata.get("affected_batches", 0) > 0:
|
||||
variant = variants.get("with_batches")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for product names list variants
|
||||
if "product_names" in metadata and metadata.get("product_names"):
|
||||
variant = variants.get("with_names")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Check for time duration variants
|
||||
if "hours_overdue" in metadata:
|
||||
variant = variants.get("with_hours")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
if "days_overdue" in metadata:
|
||||
variant = variants.get("with_days")
|
||||
if variant:
|
||||
return variant
|
||||
|
||||
# Default to generic variant
|
||||
return variants.get("generic", variants[list(variants.keys())[0]])
|
||||
|
||||
def _format_date(self, date_value: Any) -> str:
|
||||
"""
|
||||
Format date for display.
|
||||
|
||||
Accepts:
|
||||
- ISO string: "2025-12-10"
|
||||
- datetime object
|
||||
- date object
|
||||
|
||||
Returns: ISO format "YYYY-MM-DD"
|
||||
"""
|
||||
if isinstance(date_value, str):
|
||||
# Already a string, might be ISO format
|
||||
try:
|
||||
dt = datetime.fromisoformat(date_value.replace('Z', '+00:00'))
|
||||
return dt.date().isoformat()
|
||||
except:
|
||||
return date_value
|
||||
|
||||
if isinstance(date_value, datetime):
|
||||
return date_value.date().isoformat()
|
||||
|
||||
if hasattr(date_value, 'isoformat'):
|
||||
return date_value.isoformat()
|
||||
|
||||
return str(date_value)
|
||||
|
||||
def _format_day_name(self, date_value: Any) -> str:
|
||||
"""
|
||||
Format day name with date.
|
||||
|
||||
Example: "miércoles 10 de diciembre"
|
||||
|
||||
Note: Frontend will handle localization.
|
||||
For now, return ISO date and let frontend format.
|
||||
"""
|
||||
iso_date = self._format_date(date_value)
|
||||
|
||||
try:
|
||||
dt = datetime.fromisoformat(iso_date)
|
||||
# Frontend will use this to format in user's language
|
||||
return iso_date
|
||||
except:
|
||||
return iso_date
|
||||
|
||||
def _format_datetime(self, datetime_value: Any) -> str:
|
||||
"""
|
||||
Format datetime for display.
|
||||
|
||||
Returns: ISO 8601 format with timezone
|
||||
"""
|
||||
if isinstance(datetime_value, str):
|
||||
return datetime_value
|
||||
|
||||
if isinstance(datetime_value, datetime):
|
||||
return datetime_value.isoformat()
|
||||
|
||||
if hasattr(datetime_value, 'isoformat'):
|
||||
return datetime_value.isoformat()
|
||||
|
||||
return str(datetime_value)
|
||||
Reference in New Issue
Block a user