#!/usr/bin/env python3 """ Comprehensive Dashboard Demo Seed Script Seeds ALL dashboard scenarios to showcase the complete JTBD-aligned dashboard: 1. Health Status (green/yellow/red with tri-state checklist) 2. Unified Action Queue (URGENT/TODAY/WEEK time-based grouping) 3. Execution Progress (production/deliveries/approvals tracking) 4. Orchestration Summary (AI automated vs user needed) 5. Stock Receipt Modal scenarios 6. AI Prevented Issues showcase 7. Alert Hub with all alert types This creates a realistic dashboard state with: - Actions in all time groups (urgent <6h, today <24h, week <7d) - Execution progress with on_track/at_risk states - AI prevented issues with savings - Deliveries ready for stock receipt - Production batches in various states - Purchase orders needing approval Usage: python services/demo_session/scripts/seed_dashboard_comprehensive.py Environment Variables: RABBITMQ_URL: RabbitMQ connection URL (default: amqp://guest:guest@localhost:5672/) DEMO_TENANT_ID: Tenant ID to seed data for (default: demo-tenant-bakery-ia) """ import asyncio import os import sys import uuid from datetime import datetime, timedelta, timezone from pathlib import Path from typing import List, Dict, Any # Add project root to path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) from shared.messaging.rabbitmq import RabbitMQClient from shared.schemas.alert_types import AlertTypeConstants import structlog logger = structlog.get_logger() # Configuration DEMO_TENANT_ID = os.getenv('DEMO_TENANT_ID', 'demo-tenant-bakery-ia') # Build RabbitMQ URL from individual components or use direct URL RABBITMQ_URL = os.getenv('RABBITMQ_URL') if not RABBITMQ_URL: rabbitmq_host = os.getenv('RABBITMQ_HOST', 'localhost') rabbitmq_port = os.getenv('RABBITMQ_PORT', '5672') rabbitmq_user = os.getenv('RABBITMQ_USER', 'guest') rabbitmq_password = os.getenv('RABBITMQ_PASSWORD', 'guest') RABBITMQ_URL = f'amqp://{rabbitmq_user}:{rabbitmq_password}@{rabbitmq_host}:{rabbitmq_port}/' # Demo entity IDs FLOUR_ID = "flour-tipo-55" YEAST_ID = "yeast-fresh" BUTTER_ID = "butter-french" SUGAR_ID = "sugar-white" CHOCOLATE_ID = "chocolate-dark" CROISSANT_PRODUCT = "croissant-mantequilla" BAGUETTE_PRODUCT = "baguette-traditional" CHOCOLATE_CAKE_PRODUCT = "chocolate-cake" SUPPLIER_FLOUR = "supplier-harinera" SUPPLIER_YEAST = "supplier-levadura" SUPPLIER_DAIRY = "supplier-lacteos" def utc_now() -> datetime: """Get current UTC time""" return datetime.now(timezone.utc) # ============================================================ # 1. UNIFIED ACTION QUEUE - Time-Based Grouping # ============================================================ def create_urgent_actions() -> List[Dict[str, Any]]: """ Create URGENT actions (<6 hours deadline) These appear in the πŸ”΄ URGENT section """ now = utc_now() return [ # PO Approval Escalation - 2h deadline { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'procurement', 'alert_type': AlertTypeConstants.PO_APPROVAL_ESCALATION, 'title': 'URGENT: PO Approval Needed - Yeast Supplier', 'message': 'Purchase order #PO-2024-089 has been pending for 72 hours. Production batch depends on this delivery tomorrow morning.', 'alert_metadata': { 'po_id': 'po-2024-089', 'po_number': 'PO-2024-089', 'supplier_id': SUPPLIER_YEAST, 'supplier_name': 'Levadura Fresh S.L.', 'supplier_phone': '+34-555-1234', 'total_amount': 450.00, 'currency': 'EUR', 'item_categories': ['Yeast', 'Leavening Agents'], 'delivery_date': (now + timedelta(hours=10)).isoformat(), 'batch_id': 'batch-croissants-tomorrow', 'batch_name': 'Croissants Butter - Morning Batch', 'financial_impact_eur': 1200, 'orders_affected': 8, 'escalation': { 'aged_hours': 72, 'priority_boost': 20, 'reason': 'pending_over_72h' }, 'urgency_context': { 'deadline': (now + timedelta(hours=2)).isoformat(), 'time_until_consequence_hours': 2, 'auto_action_countdown_seconds': 7200, # 2h countdown 'can_wait_until_tomorrow': False } }, 'timestamp': (now - timedelta(hours=72)).isoformat() }, # Delivery Overdue - Needs immediate action { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'procurement', 'alert_type': AlertTypeConstants.DELIVERY_OVERDUE, 'title': 'Delivery Overdue: Flour Delivery', 'message': 'Expected delivery from Harinera San JosΓ© is 4 hours overdue. Contact supplier immediately.', 'alert_metadata': { 'po_id': 'po-2024-085', 'po_number': 'PO-2024-085', 'supplier_id': SUPPLIER_FLOUR, 'supplier_name': 'Harinera San JosΓ©', 'supplier_phone': '+34-555-5678', 'supplier_email': 'pedidos@harinerasj.es', 'ingredient_id': FLOUR_ID, 'ingredient_name': 'Harina Tipo 55', 'quantity_expected': 500, 'unit': 'kg', 'expected_delivery': (now - timedelta(hours=4)).isoformat(), 'hours_overdue': 4, 'stock_runout_hours': 18, 'batches_at_risk': ['batch-baguettes-001', 'batch-croissants-002'], 'urgency_context': { 'deadline': (now + timedelta(hours=3)).isoformat(), 'time_until_consequence_hours': 3, 'can_wait_until_tomorrow': False } }, 'timestamp': (now - timedelta(hours=4)).isoformat() }, # Production Batch At Risk - 5h window { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'production', 'alert_type': AlertTypeConstants.BATCH_AT_RISK, 'title': 'Batch At Risk: Missing Ingredients', 'message': 'Batch "Chocolate Cakes Evening" scheduled in 5 hours but missing 3kg dark chocolate.', 'alert_metadata': { 'batch_id': 'batch-chocolate-cake-evening', 'batch_name': 'Chocolate Cakes Evening', 'product_id': CHOCOLATE_CAKE_PRODUCT, 'product_name': 'Chocolate Cake Premium', 'planned_start': (now + timedelta(hours=5)).isoformat(), 'missing_ingredients': [ {'ingredient_id': CHOCOLATE_ID, 'name': 'Dark Chocolate 70%', 'needed': 3, 'available': 0, 'unit': 'kg'} ], 'orders_dependent': 5, 'financial_impact_eur': 380, 'urgency_context': { 'deadline': (now + timedelta(hours=5)).isoformat(), 'time_until_consequence_hours': 5, 'can_wait_until_tomorrow': False } }, 'timestamp': now.isoformat() } ] def create_today_actions() -> List[Dict[str, Any]]: """ Create TODAY actions (<24 hours deadline) These appear in the 🟑 TODAY section """ now = utc_now() return [ # PO Approval Needed - Standard priority { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'procurement', 'alert_type': AlertTypeConstants.PO_APPROVAL_NEEDED, 'title': 'PO Approval: Butter & Dairy Products', 'message': 'Purchase order for weekly dairy delivery needs your approval. Delivery scheduled for Friday.', 'alert_metadata': { 'po_id': 'po-2024-090', 'po_number': 'PO-2024-090', 'supplier_id': SUPPLIER_DAIRY, 'supplier_name': 'LΓ‘cteos Frescos Madrid', 'supplier_phone': '+34-555-9012', 'total_amount': 890.50, 'currency': 'EUR', 'item_categories': ['Butter', 'Cream', 'Milk'], 'delivery_date': (now + timedelta(days=2)).isoformat(), 'line_items': [ {'ingredient': 'French Butter', 'quantity': 20, 'unit': 'kg', 'price': 12.50}, {'ingredient': 'Heavy Cream', 'quantity': 15, 'unit': 'L', 'price': 4.20}, {'ingredient': 'Whole Milk', 'quantity': 30, 'unit': 'L', 'price': 1.80} ], 'urgency_context': { 'deadline': (now + timedelta(hours=20)).isoformat(), 'time_until_consequence_hours': 20, 'can_wait_until_tomorrow': True } }, 'timestamp': (now - timedelta(hours=6)).isoformat() }, # Delivery Arriving Soon - Needs preparation { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'procurement', 'alert_type': AlertTypeConstants.DELIVERY_ARRIVING_SOON, 'title': 'Delivery Arriving in 8 Hours: Sugar & Ingredients', 'message': 'Prepare for incoming delivery. Stock receipt will be required.', 'alert_metadata': { 'po_id': 'po-2024-088', 'po_number': 'PO-2024-088', 'supplier_id': 'supplier-ingredients', 'supplier_name': 'Ingredientes Premium', 'supplier_phone': '+34-555-3456', 'delivery_id': 'delivery-2024-088', 'expected_arrival': (now + timedelta(hours=8)).isoformat(), 'item_count': 5, 'total_weight_kg': 250, 'requires_stock_receipt': True, 'warehouse_location': 'Warehouse A - Section 3', 'line_items': [ {'ingredient': 'White Sugar', 'quantity': 100, 'unit': 'kg'}, {'ingredient': 'Brown Sugar', 'quantity': 50, 'unit': 'kg'}, {'ingredient': 'Vanilla Extract', 'quantity': 2, 'unit': 'L'} ], 'urgency_context': { 'deadline': (now + timedelta(hours=8)).isoformat(), 'time_until_consequence_hours': 8, 'can_wait_until_tomorrow': False } }, 'timestamp': now.isoformat() }, # Low Stock Warning - Today action recommended { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'inventory', 'alert_type': AlertTypeConstants.LOW_STOCK_WARNING, 'title': 'Low Stock: Fresh Yeast', 'message': 'Fresh yeast stock is low. Current: 2.5kg, Minimum: 10kg. Recommend ordering today.', 'alert_metadata': { 'ingredient_id': YEAST_ID, 'ingredient_name': 'Fresh Yeast', 'current_stock': 2.5, 'minimum_stock': 10, 'maximum_stock': 25, 'unit': 'kg', 'daily_consumption_avg': 3.5, 'days_remaining': 0.7, 'supplier_name': 'Levadura Fresh S.L.', 'last_order_date': (now - timedelta(days=8)).isoformat(), 'recommended_order_quantity': 20, 'urgency_context': { 'stockout_risk_hours': 17, 'can_wait_until_tomorrow': True } }, 'timestamp': (now - timedelta(hours=3)).isoformat() } ] def create_week_actions() -> List[Dict[str, Any]]: """ Create WEEK actions (<7 days deadline) These appear in the 🟒 THIS WEEK section """ now = utc_now() return [ # Demand Surge Predicted - Plan ahead { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'forecasting', 'alert_type': AlertTypeConstants.DEMAND_SURGE_PREDICTED, 'title': 'Weekend Demand Surge Predicted', 'message': 'Sunny weather forecast for Saturday-Sunday. Expect 25% demand increase for croissants and pastries.', 'alert_metadata': { 'forecast_type': 'weather_based', 'weather_condition': 'sunny', 'days_affected': [ (now + timedelta(days=3)).date().isoformat(), (now + timedelta(days=4)).date().isoformat() ], 'expected_demand_increase_pct': 25, 'products_affected': [ {'product_id': CROISSANT_PRODUCT, 'product_name': 'Croissant Butter', 'increase_pct': 30}, {'product_id': BAGUETTE_PRODUCT, 'product_name': 'Baguette Traditional', 'increase_pct': 20} ], 'confidence': 0.85, 'recommended_action': 'Increase production by 25% and ensure adequate stock', 'estimated_revenue_opportunity_eur': 450, 'urgency_context': { 'deadline': (now + timedelta(days=2)).isoformat(), 'can_wait_until_tomorrow': True } }, 'timestamp': now.isoformat() }, # Stock Receipt Incomplete - Can wait { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'inventory', 'alert_type': AlertTypeConstants.STOCK_RECEIPT_INCOMPLETE, 'title': 'Stock Receipt Pending: Flour Delivery', 'message': 'Delivery received 2 days ago but stock receipt not completed. Please confirm lot details and expiration dates.', 'alert_metadata': { 'receipt_id': 'receipt-2024-012', 'po_id': 'po-2024-083', 'po_number': 'PO-2024-083', 'supplier_name': 'Harinera San JosΓ©', 'delivery_date': (now - timedelta(days=2)).isoformat(), 'days_since_delivery': 2, 'line_items_pending': 3, 'total_items': 3, 'requires_lot_tracking': True, 'requires_expiration': True, 'urgency_context': { 'deadline': (now + timedelta(days=5)).isoformat(), 'can_wait_until_tomorrow': True } }, 'timestamp': (now - timedelta(days=2)).isoformat() } ] # ============================================================ # 2. AI PREVENTED ISSUES - Showcase AI Value # ============================================================ def create_prevented_issues() -> List[Dict[str, Any]]: """ Create alerts showing AI prevented issues (type_class: prevented_issue) These show in the Health Status Card and Prevented Issues Card """ now = utc_now() return [ # Prevented Stockout - PO created automatically { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'inventory', 'alert_type': 'ai_prevented_stockout', 'title': 'βœ… AI Prevented Stockout: Flour', 'message': 'AI detected upcoming stockout and created purchase order automatically. Delivery arriving Friday.', 'alert_metadata': { 'type_class': 'prevented_issue', 'priority_level': 'info', 'ingredient_id': FLOUR_ID, 'ingredient_name': 'Harina Tipo 55', 'prevented_risk': 'stockout', 'ai_action_taken': 'purchase_order_created', 'po_id': 'po-2024-091', 'po_number': 'PO-2024-091', 'quantity_ordered': 500, 'unit': 'kg', 'supplier_name': 'Harinera San JosΓ©', 'delivery_date': (now + timedelta(days=2)).isoformat(), 'estimated_savings_eur': 250, 'orchestrator_context': { 'already_addressed': True, 'action_type': 'purchase_order', 'action_id': 'po-2024-091', 'action_status': 'pending_approval', 'reasoning': 'Detected stock would run out in 1.8 days based on historical consumption patterns' }, 'business_impact': { 'prevented_stockout_hours': 43, 'affected_orders_prevented': 12, 'production_batches_secured': 4 }, 'ai_reasoning_summary': 'Analyzed consumption patterns and detected stockout in 43 hours. Created PO for 500kg to arrive Friday, preventing disruption to 4 production batches.' }, 'timestamp': (now - timedelta(hours=12)).isoformat() }, # Prevented Waste - Production adjusted { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'production', 'alert_type': 'ai_prevented_waste', 'title': 'βœ… AI Prevented Waste: Reduced Monday Production', 'message': 'AI detected post-weekend demand pattern and reduced Monday production by 15%, preventing €120 in waste.', 'alert_metadata': { 'type_class': 'prevented_issue', 'priority_level': 'info', 'product_id': BAGUETTE_PRODUCT, 'product_name': 'Baguette Traditional', 'prevented_risk': 'waste', 'ai_action_taken': 'production_adjusted', 'reduction_pct': 15, 'units_reduced': 45, 'day_of_week': 'Monday', 'historical_pattern': 'post_weekend_low_demand', 'estimated_savings_eur': 120, 'orchestrator_context': { 'already_addressed': True, 'action_type': 'production_batch', 'action_id': 'batch-baguettes-monday-adjusted', 'action_status': 'completed', 'reasoning': 'Historical data shows 18% demand drop on Mondays following sunny weekends' }, 'business_impact': { 'waste_prevented_kg': 12, 'waste_reduction_pct': 15 }, 'ai_reasoning_summary': 'Detected consistent Monday demand drop (18% avg) in post-weekend data. Adjusted production to prevent overproduction and waste.' }, 'timestamp': (now - timedelta(days=1)).isoformat() }, # Prevented Production Delay - Batch rescheduled { 'id': str(uuid.uuid4()), 'tenant_id': DEMO_TENANT_ID, 'item_type': 'alert', 'service': 'production', 'alert_type': 'ai_prevented_delay', 'title': 'βœ… AI Prevented Delay: Rescheduled Conflicting Batches', 'message': 'AI detected oven capacity conflict and rescheduled batches to optimize throughput.', 'alert_metadata': { 'type_class': 'prevented_issue', 'priority_level': 'info', 'prevented_risk': 'production_delay', 'ai_action_taken': 'batch_rescheduled', 'batches_affected': 3, 'equipment_id': 'oven-001', 'equipment_name': 'Industrial Oven #1', 'capacity_utilization_before': 110, 'capacity_utilization_after': 95, 'time_saved_minutes': 45, 'estimated_savings_eur': 85, 'orchestrator_context': { 'already_addressed': True, 'action_type': 'batch_optimization', 'action_status': 'completed', 'reasoning': 'Detected overlapping batch schedules would exceed oven capacity by 10%' }, 'business_impact': { 'orders_on_time': 8, 'customer_satisfaction_impact': 'high' }, 'ai_reasoning_summary': 'Identified capacity conflict in oven schedule. Rescheduled 3 batches to maintain on-time delivery for 8 orders.' }, 'timestamp': (now - timedelta(hours=18)).isoformat() } ] # ============================================================ # 3. EXECUTION PROGRESS - Production/Deliveries/Approvals # ============================================================ def create_production_batches() -> List[Dict[str, Any]]: """ Create production batch data for execution progress tracking """ now = utc_now() return [ # Completed batch { 'batch_id': 'batch-morning-croissants', 'product_name': 'Croissant Butter', 'quantity': 120, 'status': 'completed', 'planned_start': (now - timedelta(hours=4)).isoformat(), 'actual_start': (now - timedelta(hours=4, minutes=5)).isoformat(), 'actual_end': (now - timedelta(hours=1)).isoformat() }, # In progress batch { 'batch_id': 'batch-baguettes-lunch', 'product_name': 'Baguette Traditional', 'quantity': 80, 'status': 'in_progress', 'planned_start': (now - timedelta(hours=2)).isoformat(), 'actual_start': (now - timedelta(hours=2, minutes=10)).isoformat(), 'progress_pct': 65 }, # Pending batches { 'batch_id': 'batch-afternoon-pastries', 'product_name': 'Mixed Pastries', 'quantity': 50, 'status': 'pending', 'planned_start': (now + timedelta(hours=1)).isoformat() }, { 'batch_id': 'batch-evening-bread', 'product_name': 'Artisan Bread Assortment', 'quantity': 40, 'status': 'pending', 'planned_start': (now + timedelta(hours=3)).isoformat() } ] # ============================================================ # MAIN SEEDING FUNCTION # ============================================================ async def seed_comprehensive_dashboard(): """ Seed comprehensive dashboard demo data Creates realistic dashboard state showcasing: - All time-based action groups (urgent/today/week) - AI prevented issues with savings - Execution progress tracking - Stock receipt scenarios - Full alert type coverage """ print("\n" + "="*80) print("πŸš€ SEEDING COMPREHENSIVE DASHBOARD DEMO DATA") print("="*80 + "\n") print(f"πŸ“‹ Configuration:") print(f" Tenant ID: {DEMO_TENANT_ID}") print(f" RabbitMQ: {RABBITMQ_URL}") print() try: # Initialize RabbitMQ logger.info("Connecting to RabbitMQ", url=RABBITMQ_URL) rabbitmq_client = RabbitMQClient(RABBITMQ_URL, 'dashboard-demo-seeder') await rabbitmq_client.connect() # Collect all alerts all_alerts = [] all_alerts.extend(create_urgent_actions()) all_alerts.extend(create_today_actions()) all_alerts.extend(create_week_actions()) all_alerts.extend(create_prevented_issues()) print(f"πŸ“Š Dashboard Scenarios to Seed:") print(f" πŸ”΄ URGENT actions (<6h): {len(create_urgent_actions())}") print(f" 🟑 TODAY actions (<24h): {len(create_today_actions())}") print(f" 🟒 WEEK actions (<7d): {len(create_week_actions())}") print(f" βœ… AI Prevented Issues: {len(create_prevented_issues())}") print(f" πŸ“¦ Total Alerts: {len(all_alerts)}") print() # Publish alerts print("πŸ“€ Publishing Alerts:") print("-" * 80) success_count = 0 for i, alert in enumerate(all_alerts, 1): routing_key = f"{alert['item_type']}.{alert['service']}.{alert['alert_type']}" success = await rabbitmq_client.publish_event( exchange_name='alerts.exchange', routing_key=routing_key, event_data=alert, persistent=True ) if success: success_count += 1 status = "βœ…" else: status = "❌" logger.warning("Failed to publish alert", alert_id=alert['id']) # Determine time group emoji if 'escalation' in alert.get('alert_metadata', {}) or \ alert.get('alert_metadata', {}).get('urgency_context', {}).get('time_until_consequence_hours', 999) < 6: group = "πŸ”΄ URGENT" elif alert.get('alert_metadata', {}).get('urgency_context', {}).get('time_until_consequence_hours', 999) < 24: group = "🟑 TODAY" elif alert.get('alert_metadata', {}).get('type_class') == 'prevented_issue': group = "βœ… PREVENTED" else: group = "🟒 WEEK" print(f" {i:2d}. {status} [{group}] {alert['title']}") print() print(f"βœ… Published {success_count}/{len(all_alerts)} alerts successfully") print() await rabbitmq_client.disconnect() # Print dashboard preview print("="*80) print("πŸ“Š EXPECTED DASHBOARD STATE") print("="*80) print() print("πŸ₯ HEALTH STATUS:") print(" Status: YELLOW (actions needed)") print(" Checklist:") print(" ⚑ AI Handled: 3 issues prevented (€455 saved)") print(" ⚠️ Needs You: 3 urgent actions, 3 today actions") print() print("πŸ“‹ ACTION QUEUE:") print(" πŸ”΄ URGENT (<6h): 3 actions") print(" - PO Approval Escalation (2h deadline)") print(" - Delivery Overdue (4h late)") print(" - Batch At Risk (5h until start)") print() print(" 🟑 TODAY (<24h): 3 actions") print(" - PO Approval: Dairy Products") print(" - Delivery Arriving Soon (8h)") print(" - Low Stock: Fresh Yeast") print() print(" 🟒 THIS WEEK (<7d): 2 actions") print(" - Weekend Demand Surge") print(" - Stock Receipt Incomplete") print() print("πŸ“Š EXECUTION PROGRESS:") print(" Production: 2/4 batches (on_track)") print(" βœ… Completed: 1") print(" πŸ”„ In Progress: 1") print(" ⏳ Pending: 2") print() print(" Deliveries: Status depends on real PO data") print(" Approvals: 2 pending") print() print("βœ… AI PREVENTED ISSUES:") print(" Total Prevented: 3 issues") print(" Total Savings: €455") print(" Details:") print(" - Prevented stockout (€250 saved)") print(" - Prevented waste (€120 saved)") print(" - Prevented delay (€85 saved)") print() print("="*80) print("πŸŽ‰ DASHBOARD DEMO SEEDED SUCCESSFULLY!") print("="*80) print() print("Next Steps:") print(" 1. Verify alert-processor is running:") print(" kubectl get pods -l app.kubernetes.io/name=alert-processor-service") print() print(" 2. Check alert enrichment logs:") print(" kubectl logs -f deployment/alert-processor-service | grep 'enriched_alert'") print() print(" 3. View dashboard:") print(" http://localhost:3000/dashboard") print() print(" 4. Test smart actions:") print(" - Approve/Reject PO from action queue") print(" - Mark delivery as received (opens stock receipt modal)") print(" - Call supplier (initiates phone call)") print(" - Adjust production (navigates to production page)") print() except Exception as e: logger.error("Failed to seed dashboard demo", error=str(e), exc_info=True) 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 alerts.exchange exists") print(" β€’ Check alert-processor service logs for errors") raise if __name__ == "__main__": # Load environment variables from dotenv import load_dotenv load_dotenv() # Run seeder asyncio.run(seed_comprehensive_dashboard())