Files
bakery-ia/services/demo_session/scripts/seed_enriched_alert_demo.py
2025-12-05 20:07:01 +01:00

427 lines
17 KiB
Python

#!/usr/bin/env python3
"""
Seed demo data for Unified Alert Service
This script creates demo alerts that showcase the enrichment capabilities:
- Low stock (AI already handled - prevented issue)
- Supplier delay (action needed - critical)
- Waste trend (trend warning - standard)
- Orchestrator actions (for context enrichment)
The unified alert-processor service automatically enriches all alerts with:
- Multi-factor priority scoring
- Orchestrator context (AI actions)
- Business impact analysis
- Smart actions with deep links
- Timing intelligence
Usage:
python services/demo_session/scripts/seed_enriched_alert_demo.py
"""
import asyncio
import os
import sys
import uuid
from datetime import datetime, timedelta
from pathlib import Path
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from shared.messaging import RabbitMQClient
import structlog
logger = structlog.get_logger()
# Demo tenant ID (should match existing demo tenant)
DEMO_TENANT_ID = "demo-tenant-bakery-ia"
# Demo entity IDs (should match existing demo data)
FLOUR_INGREDIENT_ID = "flour-tipo-55"
YEAST_INGREDIENT_ID = "yeast-fresh"
CROISSANT_PRODUCT_ID = "croissant-mantequilla"
CROISSANT_BATCH_ID = "batch-croissants-001"
YEAST_SUPPLIER_ID = "supplier-levadura-fresh"
FLOUR_PO_ID = "po-flour-demo-001"
# Demo alerts for unified alert-processor (automatically enriched)
DEMO_ALERTS = [
# Alert 1: Low Stock - AI Already Handled (Prevented Issue)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'item_type': 'alert',
'service': 'inventory',
'type': 'low_stock',
'severity': 'warning', # Will be enriched with priority score ~71
'title': 'Bajo Stock: Harina Tipo 55',
'message': 'Stock: 45kg, Mínimo: 200kg',
'actions': [], # Will be enhanced with smart actions
'metadata': {
'ingredient_id': FLOUR_INGREDIENT_ID,
'ingredient_name': 'Harina Tipo 55',
'current_stock': 45,
'minimum_stock': 200,
'unit': 'kg',
'supplier_name': 'Harinera San José',
'last_order_date': (datetime.utcnow() - timedelta(days=7)).isoformat(),
'i18n': {
'title_key': 'alerts.low_stock_warning.title',
'message_key': 'alerts.low_stock_warning.message_generic',
'title_params': {'ingredient_name': 'Harina Tipo 55'},
'message_params': {
'ingredient_name': 'Harina Tipo 55',
'current_stock': 45,
'minimum_stock': 200
}
}
},
'timestamp': datetime.utcnow().isoformat()
},
# Alert 2: Supplier Delay - Action Needed (Critical)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'item_type': 'alert',
'service': 'procurement',
'type': 'supplier_delay',
'severity': 'critical', # Will be enriched with priority score ~92
'actions': [], # Will be enhanced with smart actions
'title': 'Retraso de Proveedor: Levadura Fresh',
'message': 'Entrega retrasada 24 horas. Levadura necesaria para producción de mañana.',
'metadata': {
'supplier_id': YEAST_SUPPLIER_ID,
'supplier_name': 'Levadura Fresh',
'supplier_phone': '+34-555-1234',
'supplier_email': 'pedidos@levadura-fresh.es',
'ingredient_id': YEAST_INGREDIENT_ID,
'ingredient_name': 'Levadura Fresca',
'batch_id': CROISSANT_BATCH_ID,
'batch_name': 'Croissants Mantequilla Mañana',
'orders_affected': 3,
'financial_impact_eur': 450,
'deadline': (datetime.utcnow() + timedelta(hours=6)).isoformat(),
'quantity_needed': 15,
'unit': 'kg',
'i18n': {
'title_key': 'alerts.supplier_delay.title',
'message_key': 'alerts.supplier_delay.message',
'title_params': {'supplier_name': 'Levadura Fresh'},
'message_params': {
'supplier_name': 'Levadura Fresh',
'ingredient_name': 'Levadura Fresca',
'po_id': 'PO-DEMO-123',
'new_delivery_date': (datetime.utcnow() + timedelta(hours=24)).strftime('%Y-%m-%d'),
'original_delivery_date': (datetime.utcnow() - timedelta(hours=24)).strftime('%Y-%m-%d')
}
}
},
'timestamp': datetime.utcnow().isoformat()
},
# Alert 3: Waste Trend - Trend Warning (Standard)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'item_type': 'alert',
'service': 'production',
'type': 'waste_trend',
'severity': 'medium', # Will be enriched with priority score ~58
'actions': [], # Will be enhanced with smart actions
'title': 'Tendencia de Desperdicio: Croissants',
'message': 'Desperdicio aumentó 15% en 3 días. Patrón detectado: sobreproducción miércoles.',
'metadata': {
'product_id': CROISSANT_PRODUCT_ID,
'product_name': 'Croissant Mantequilla',
'waste_percentage': 23,
'baseline_percentage': 8,
'trend_days': 3,
'pattern': 'wednesday_overproduction',
'pattern_description': 'Desperdicio consistentemente alto los miércoles (18%, 21%, 23%)',
'financial_impact_eur': 180,
'recommendation': 'Reducir producción miércoles en 25 unidades',
'confidence': 0.85,
'historical_data': [
{'day': 'Mon', 'waste_pct': 7},
{'day': 'Tue', 'waste_pct': 9},
{'day': 'Wed', 'waste_pct': 23},
{'day': 'Thu', 'waste_pct': 8},
{'day': 'Fri', 'waste_pct': 6}
],
'i18n': {
'title_key': 'alerts.waste_trend.title',
'message_key': 'alerts.waste_trend.message',
'title_params': {'product_name': 'Croissant Mantequilla'},
'message_params': {
'product_name': 'Croissant Mantequilla',
'spike_percent': 15,
'trend_days': 3,
'pattern': 'wednesday_overproduction'
}
}
},
'timestamp': datetime.utcnow().isoformat()
},
# Alert 4: Forecast Anomaly - Information (Low Priority)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'item_type': 'alert',
'service': 'forecasting',
'type': 'forecast_updated',
'severity': 'low', # Will be enriched with priority score ~35
'actions': [], # Will be enhanced with smart actions
'title': 'Previsión Actualizada: Fin de Semana Soleado',
'message': 'Pronóstico meteorológico actualizado: soleado sábado y domingo. Aumento de demanda esperado.',
'metadata': {
'forecast_type': 'weather_based',
'weather_condition': 'sunny',
'days_affected': ['2024-11-23', '2024-11-24'],
'expected_demand_increase_pct': 15,
'confidence': 0.78,
'recommended_action': 'Aumentar producción croissants y pan rústico 15%',
'i18n': {
'title_key': 'alerts.demand_surge_weekend.title',
'message_key': 'alerts.demand_surge_weekend.message',
'title_params': {'weekend_date': (datetime.utcnow() + timedelta(days=1)).strftime('%Y-%m-%d')},
'message_params': {
'surge_percent': 15,
'date': (datetime.utcnow() + timedelta(days=1)).strftime('%Y-%m-%d'),
'products': ['croissants', 'pan rustico']
}
}
},
'timestamp': datetime.utcnow().isoformat()
},
# Alert 5: Equipment Maintenance - Action Needed (Medium)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'item_type': 'alert',
'service': 'production',
'type': 'equipment_maintenance',
'severity': 'medium', # Will be enriched with priority score ~65
'actions': [], # Will be enhanced with smart actions
'title': 'Mantenimiento Programado: Horno Industrial',
'message': 'Horno principal requiere mantenimiento en 48 horas según calendario.',
'metadata': {
'equipment_id': 'oven-001',
'equipment_name': 'Horno Industrial Principal',
'equipment_type': 'oven',
'maintenance_type': 'preventive',
'scheduled_date': (datetime.utcnow() + timedelta(hours=48)).isoformat(),
'estimated_duration_hours': 3,
'last_maintenance': (datetime.utcnow() - timedelta(days=90)).isoformat(),
'maintenance_interval_days': 90,
'supplier_contact': 'TecnoHornos Madrid',
'supplier_phone': '+34-555-6789',
'i18n': {
'title_key': 'alerts.maintenance_required.title',
'message_key': 'alerts.maintenance_required.message_with_hours',
'title_params': {'equipment_name': 'Horno Industrial Principal'},
'message_params': {
'equipment_name': 'Horno Industrial Principal',
'hours_until': 48,
'maintenance_date': (datetime.utcnow() + timedelta(hours=48)).strftime('%Y-%m-%d')
}
}
},
'timestamp': datetime.utcnow().isoformat()
}
]
# Demo orchestrator actions (for context enrichment)
DEMO_ORCHESTRATOR_ACTIONS = [
# Action 1: PO Created for Flour (provides context for Alert 1)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'action_type': 'purchase_order_created',
'reasoning_type': 'preventive',
'entity_type': 'purchase_order',
'entity_id': FLOUR_PO_ID,
'summary': 'Pedido de compra #12345 creado para 500kg harina',
'reasoning_summary': 'Detecté que el stock se agotará en 2.3 días basándome en patrones históricos de demanda. Creé pedido de compra para llegar el viernes.',
'metadata': {
'ingredient_id': FLOUR_INGREDIENT_ID,
'ingredient_name': 'Harina Tipo 55',
'quantity': 500,
'unit': 'kg',
'supplier_name': 'Harinera San José',
'po_number': 'PO-12345',
'estimated_delivery': (datetime.utcnow() + timedelta(days=2, hours=10)).isoformat(),
'estimated_savings_eur': 200,
'prevented_issue': 'stockout',
'confidence': 0.92,
'demand_forecast_method': 'historical_patterns',
'stock_runout_days': 2.3
},
'created_at': (datetime.utcnow() - timedelta(hours=2)).isoformat()
},
# Action 2: Batch Scheduled for Weekend (weather-based)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'action_type': 'batch_scheduled',
'reasoning_type': 'weather_based',
'entity_type': 'production_batch',
'entity_id': CROISSANT_BATCH_ID,
'summary': 'Aumentada producción croissants 20% para sábado',
'reasoning_summary': 'Pronóstico meteorológico muestra fin de semana soleado. Datos históricos muestran aumento de demanda 15% en sábados soleados.',
'metadata': {
'product_id': CROISSANT_PRODUCT_ID,
'product_name': 'Croissant Mantequilla',
'quantity_increase': 25,
'quantity_increase_pct': 20,
'date': (datetime.utcnow() + timedelta(days=2)).date().isoformat(),
'weather_condition': 'sunny',
'confidence': 0.85,
'historical_sunny_saturday_demand_increase': 15,
'estimated_additional_revenue_eur': 180
},
'created_at': (datetime.utcnow() - timedelta(hours=4)).isoformat()
},
# Action 3: Waste Reduction Adjustment (for trend context)
{
'id': str(uuid.uuid4()),
'tenant_id': DEMO_TENANT_ID,
'action_type': 'production_adjusted',
'reasoning_type': 'waste_reduction',
'entity_type': 'production_plan',
'entity_id': str(uuid.uuid4()),
'summary': 'Reducida producción lunes 10% basado en patrón post-fin de semana',
'reasoning_summary': 'Detecté patrón de baja demanda post-fin de semana. Histórico muestra reducción 12% demanda lunes. Ajusté producción para prevenir desperdicio.',
'metadata': {
'day_of_week': 'monday',
'reduction_pct': 10,
'historical_demand_drop_pct': 12,
'products_affected': ['Croissant Mantequilla', 'Pan Rústico'],
'estimated_waste_prevented_eur': 85,
'confidence': 0.78
},
'created_at': (datetime.utcnow() - timedelta(hours=20)).isoformat()
}
]
async def seed_enriched_alert_demo():
"""
Seed demo data for unified alert service
This creates alerts that will be automatically enriched by alert-processor with:
- Multi-factor priority scoring
- Orchestrator context (AI actions)
- Business impact analysis
- Smart actions
- Timing intelligence
"""
print("\n" + "="*70)
print("🚀 SEEDING DEMO DATA FOR UNIFIED ALERT SERVICE")
print("="*70 + "\n")
rabbitmq_url = os.getenv('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672/')
try:
# Initialize RabbitMQ client
logger.info("Connecting to RabbitMQ", url=rabbitmq_url)
rabbitmq_client = RabbitMQClient(rabbitmq_url, 'demo-seeder')
await rabbitmq_client.connect()
# Publish demo alerts (will be automatically enriched)
print("\n📤 Publishing Alerts (Automatic Enrichment):")
print("-" * 70)
for i, alert in enumerate(DEMO_ALERTS, 1):
routing_key = f"{alert['item_type']}.{alert['service']}.{alert['type']}"
# Use publish_event method (correct API)
success = await rabbitmq_client.publish_event(
exchange_name='alerts.exchange',
routing_key=routing_key,
event_data=alert,
persistent=True
)
if not success:
logger.warning("Failed to publish alert", alert_id=alert['id'])
continue
print(f" {i}. ✅ {alert['title']}")
print(f" Service: {alert['service']}")
print(f" Severity: {alert['severity']} (will be enriched)")
print(f" Routing Key: {routing_key}")
print()
# Note about orchestrator actions
print("\n📊 Orchestrator Actions (for Context Enrichment):")
print("-" * 70)
print("NOTE: Orchestrator actions should be seeded via orchestrator service.")
print("These provide context for 'AI already handled' enrichment.\n")
for i, action in enumerate(DEMO_ORCHESTRATOR_ACTIONS, 1):
print(f" {i}. {action['summary']}")
print(f" Type: {action['reasoning_type']}")
print(f" Created: {action['created_at']}")
print()
print("💡 TIP: To seed orchestrator actions, run:")
print(" python services/orchestrator/scripts/seed_demo_actions.py")
print()
await rabbitmq_client.disconnect()
print("="*70)
print("✅ DEMO ALERTS SEEDED SUCCESSFULLY!")
print("="*70)
print()
print("Next steps:")
print(" 1. Verify alert-processor service is running:")
print(" kubectl get pods -l app.kubernetes.io/name=alert-processor-service")
print()
print(" 2. Check logs for automatic enrichment:")
print(" kubectl logs -f deployment/alert-processor-service")
print()
print(" 3. View enriched alerts in dashboard:")
print(" http://localhost:3000/dashboard")
print()
print("Expected automatic enrichment:")
print(" • Low Stock Alert → Important (71) - Prevented Issue + Smart Actions")
print(" • Supplier Delay → Critical (92) - Action Needed + Deep Links")
print(" • Waste Trend → Standard (58) - Trend Warning + AI Reasoning")
print(" • Forecast Update → Info (35) - Information")
print(" • Equipment Maint → Standard (65) - Action Needed + Timing")
print()
print("All alerts enriched with:")
print(" ✓ Multi-factor priority scores")
print(" ✓ Orchestrator context (AI actions)")
print(" ✓ Business impact analysis")
print(" ✓ Smart actions with deep links")
print(" ✓ Timing intelligence")
print()
except Exception as e:
logger.error("Failed to seed demo data", error=str(e))
print(f"\n❌ ERROR: {str(e)}")
print("\nTroubleshooting:")
print(" • Verify RabbitMQ is running: kubectl get pods | grep rabbitmq")
print(" • Check RABBITMQ_URL environment variable")
print(" • Ensure raw_alerts exchange can be created")
raise
if __name__ == "__main__":
# Load environment variables
from dotenv import load_dotenv
load_dotenv()
# Run seeder
asyncio.run(seed_enriched_alert_demo())