723 lines
29 KiB
Python
Executable File
723 lines
29 KiB
Python
Executable File
#!/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())
|