344 lines
12 KiB
Python
344 lines
12 KiB
Python
"""
|
|
Event Classification Schema
|
|
|
|
This module defines the three-tier event model that separates:
|
|
- ALERTS: Actionable events requiring user decision
|
|
- NOTIFICATIONS: Informational state changes (FYI only)
|
|
- RECOMMENDATIONS: Advisory suggestions from AI
|
|
|
|
This replaces the old conflated "alert" system with semantic clarity.
|
|
"""
|
|
|
|
from enum import Enum
|
|
from typing import Any, Dict, List, Optional
|
|
from datetime import datetime
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class EventClass(str, Enum):
|
|
"""
|
|
Top-level event classification.
|
|
|
|
- ALERT: Actionable, requires user decision, has smart actions
|
|
- NOTIFICATION: Informational state change, no action needed
|
|
- RECOMMENDATION: Advisory suggestion, optional action
|
|
"""
|
|
ALERT = "alert"
|
|
NOTIFICATION = "notification"
|
|
RECOMMENDATION = "recommendation"
|
|
|
|
|
|
class EventDomain(str, Enum):
|
|
"""
|
|
Business domain classification for events.
|
|
Enables domain-specific dashboards and selective subscription.
|
|
"""
|
|
INVENTORY = "inventory"
|
|
PRODUCTION = "production"
|
|
SUPPLY_CHAIN = "supply_chain"
|
|
DEMAND = "demand"
|
|
OPERATIONS = "operations"
|
|
|
|
|
|
class PriorityLevel(str, Enum):
|
|
"""Priority levels for alerts and recommendations."""
|
|
CRITICAL = "critical" # 90-100: Immediate action required
|
|
IMPORTANT = "important" # 70-89: Action needed soon
|
|
STANDARD = "standard" # 50-69: Normal priority
|
|
INFO = "info" # 0-49: Low priority, informational
|
|
|
|
|
|
class AlertTypeClass(str, Enum):
|
|
"""
|
|
Alert-specific classification (only applies to EventClass.ALERT).
|
|
"""
|
|
ACTION_NEEDED = "action_needed" # User must decide
|
|
PREVENTED_ISSUE = "prevented_issue" # AI already handled, FYI
|
|
TREND_WARNING = "trend_warning" # Pattern detected
|
|
ESCALATION = "escalation" # Time-sensitive with auto-action countdown
|
|
INFORMATION = "information" # Pure informational alert
|
|
|
|
|
|
class NotificationType(str, Enum):
|
|
"""
|
|
Notification-specific types for state changes.
|
|
"""
|
|
STATE_CHANGE = "state_change" # Entity state transition
|
|
COMPLETION = "completion" # Process/task completed
|
|
ARRIVAL = "arrival" # Entity arrived/received
|
|
DEPARTURE = "departure" # Entity left/shipped
|
|
UPDATE = "update" # General update
|
|
SYSTEM_EVENT = "system_event" # System operation
|
|
|
|
|
|
class RecommendationType(str, Enum):
|
|
"""
|
|
Recommendation-specific types.
|
|
"""
|
|
OPTIMIZATION = "optimization" # Efficiency improvement
|
|
COST_REDUCTION = "cost_reduction" # Save money
|
|
RISK_MITIGATION = "risk_mitigation" # Prevent future issues
|
|
TREND_INSIGHT = "trend_insight" # Pattern analysis
|
|
BEST_PRACTICE = "best_practice" # Suggested approach
|
|
|
|
|
|
class RawEvent(BaseModel):
|
|
"""
|
|
Base event emitted by domain services.
|
|
|
|
This is the unified schema replacing the old RawAlert.
|
|
All domain services emit RawEvents which are then conditionally enriched.
|
|
"""
|
|
tenant_id: str = Field(..., description="Tenant identifier")
|
|
|
|
# Event classification
|
|
event_class: EventClass = Field(..., description="Alert, Notification, or Recommendation")
|
|
event_domain: EventDomain = Field(..., description="Business domain (inventory, production, etc.)")
|
|
event_type: str = Field(..., description="Specific event type (e.g., 'critical_stock_shortage')")
|
|
|
|
# Core content
|
|
title: str = Field(..., description="Event title")
|
|
message: str = Field(..., description="Event message")
|
|
|
|
# Source
|
|
service: str = Field(..., description="Originating service name")
|
|
|
|
# Actions (optional, mainly for alerts)
|
|
actions: Optional[List[str]] = Field(default=None, description="Available action types")
|
|
|
|
# Metadata (domain-specific data)
|
|
event_metadata: Dict[str, Any] = Field(default_factory=dict, description="Domain-specific metadata")
|
|
|
|
# Timestamp
|
|
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Event creation time")
|
|
|
|
# Deduplication (optional)
|
|
deduplication_key: Optional[str] = Field(default=None, description="Key for deduplication")
|
|
|
|
class Config:
|
|
use_enum_values = True
|
|
|
|
|
|
class EnrichedAlert(BaseModel):
|
|
"""
|
|
Fully enriched alert with priority scoring, smart actions, and context.
|
|
Only used for EventClass.ALERT.
|
|
"""
|
|
# From RawEvent
|
|
id: str
|
|
tenant_id: str
|
|
event_domain: EventDomain
|
|
event_type: str
|
|
title: str
|
|
message: str
|
|
service: str
|
|
timestamp: datetime
|
|
|
|
# Alert-specific
|
|
type_class: AlertTypeClass
|
|
status: str # active, acknowledged, resolved, dismissed
|
|
|
|
# Priority
|
|
priority_score: int = Field(..., ge=0, le=100, description="0-100 priority score")
|
|
priority_level: PriorityLevel
|
|
|
|
# Enrichment context
|
|
orchestrator_context: Optional[Dict[str, Any]] = Field(default=None)
|
|
business_impact: Optional[Dict[str, Any]] = Field(default=None)
|
|
urgency_context: Optional[Dict[str, Any]] = Field(default=None)
|
|
user_agency: Optional[Dict[str, Any]] = Field(default=None)
|
|
|
|
# Smart actions
|
|
smart_actions: Optional[List[Dict[str, Any]]] = Field(default=None)
|
|
|
|
# AI reasoning
|
|
ai_reasoning_summary: Optional[str] = Field(default=None)
|
|
confidence_score: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
|
|
|
# Timing
|
|
timing_decision: Optional[str] = Field(default=None)
|
|
scheduled_send_time: Optional[datetime] = Field(default=None)
|
|
placement: Optional[List[str]] = Field(default=None)
|
|
|
|
# Metadata
|
|
alert_metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
class Config:
|
|
use_enum_values = True
|
|
|
|
|
|
class Notification(BaseModel):
|
|
"""
|
|
Lightweight notification for state changes.
|
|
Only used for EventClass.NOTIFICATION.
|
|
"""
|
|
# From RawEvent
|
|
id: str
|
|
tenant_id: str
|
|
event_domain: EventDomain
|
|
event_type: str
|
|
notification_type: NotificationType
|
|
title: str
|
|
message: str
|
|
service: str
|
|
timestamp: datetime
|
|
|
|
# Lightweight context
|
|
entity_type: Optional[str] = Field(default=None, description="Type of entity (batch, delivery, etc.)")
|
|
entity_id: Optional[str] = Field(default=None, description="ID of entity")
|
|
old_state: Optional[str] = Field(default=None, description="Previous state")
|
|
new_state: Optional[str] = Field(default=None, description="New state")
|
|
|
|
# Display metadata
|
|
notification_metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
# Placement (lightweight, typically just toast + panel)
|
|
placement: List[str] = Field(default_factory=lambda: ["notification_panel"])
|
|
|
|
# TTL tracking
|
|
expires_at: Optional[datetime] = Field(default=None, description="Auto-delete after this time")
|
|
|
|
class Config:
|
|
use_enum_values = True
|
|
|
|
|
|
class Recommendation(BaseModel):
|
|
"""
|
|
AI-generated recommendation with moderate enrichment.
|
|
Only used for EventClass.RECOMMENDATION.
|
|
"""
|
|
# From RawEvent
|
|
id: str
|
|
tenant_id: str
|
|
event_domain: EventDomain
|
|
event_type: str
|
|
recommendation_type: RecommendationType
|
|
title: str
|
|
message: str
|
|
service: str
|
|
timestamp: datetime
|
|
|
|
# Recommendation-specific
|
|
priority_level: PriorityLevel = Field(default=PriorityLevel.INFO)
|
|
|
|
# Context (lighter than alerts, no orchestrator queries)
|
|
estimated_impact: Optional[Dict[str, Any]] = Field(default=None, description="Estimated benefit")
|
|
suggested_actions: Optional[List[Dict[str, Any]]] = Field(default=None)
|
|
|
|
# AI reasoning
|
|
ai_reasoning_summary: Optional[str] = Field(default=None)
|
|
confidence_score: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
|
|
|
# Dismissal tracking
|
|
dismissed_at: Optional[datetime] = Field(default=None)
|
|
dismissed_by: Optional[str] = Field(default=None)
|
|
|
|
# Metadata
|
|
recommendation_metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
class Config:
|
|
use_enum_values = True
|
|
|
|
|
|
# Event type mappings for easy classification
|
|
EVENT_TYPE_TO_CLASS_MAP = {
|
|
# Alerts (actionable)
|
|
"critical_stock_shortage": (EventClass.ALERT, EventDomain.INVENTORY),
|
|
"production_delay": (EventClass.ALERT, EventDomain.PRODUCTION),
|
|
"equipment_failure": (EventClass.ALERT, EventDomain.PRODUCTION),
|
|
"po_approval_needed": (EventClass.ALERT, EventDomain.SUPPLY_CHAIN),
|
|
"delivery_overdue": (EventClass.ALERT, EventDomain.SUPPLY_CHAIN),
|
|
"temperature_breach": (EventClass.ALERT, EventDomain.INVENTORY),
|
|
"expired_products": (EventClass.ALERT, EventDomain.INVENTORY),
|
|
"low_stock_warning": (EventClass.ALERT, EventDomain.INVENTORY),
|
|
"production_ingredient_shortage": (EventClass.ALERT, EventDomain.INVENTORY),
|
|
"order_overload": (EventClass.ALERT, EventDomain.PRODUCTION),
|
|
|
|
# Notifications (informational)
|
|
"stock_received": (EventClass.NOTIFICATION, EventDomain.INVENTORY),
|
|
"stock_movement": (EventClass.NOTIFICATION, EventDomain.INVENTORY),
|
|
"batch_state_changed": (EventClass.NOTIFICATION, EventDomain.PRODUCTION),
|
|
"batch_completed": (EventClass.NOTIFICATION, EventDomain.PRODUCTION),
|
|
"orchestration_run_started": (EventClass.NOTIFICATION, EventDomain.OPERATIONS),
|
|
"orchestration_run_completed": (EventClass.NOTIFICATION, EventDomain.OPERATIONS),
|
|
"po_approved": (EventClass.NOTIFICATION, EventDomain.SUPPLY_CHAIN),
|
|
"po_sent_to_supplier": (EventClass.NOTIFICATION, EventDomain.SUPPLY_CHAIN),
|
|
"delivery_scheduled": (EventClass.NOTIFICATION, EventDomain.SUPPLY_CHAIN),
|
|
"delivery_arriving_soon": (EventClass.NOTIFICATION, EventDomain.SUPPLY_CHAIN),
|
|
"delivery_received": (EventClass.NOTIFICATION, EventDomain.SUPPLY_CHAIN),
|
|
|
|
# Recommendations (advisory)
|
|
"demand_surge_predicted": (EventClass.RECOMMENDATION, EventDomain.DEMAND),
|
|
"weather_impact_forecast": (EventClass.RECOMMENDATION, EventDomain.DEMAND),
|
|
"holiday_preparation": (EventClass.RECOMMENDATION, EventDomain.DEMAND),
|
|
"inventory_optimization_opportunity": (EventClass.RECOMMENDATION, EventDomain.INVENTORY),
|
|
"cost_reduction_suggestion": (EventClass.RECOMMENDATION, EventDomain.SUPPLY_CHAIN),
|
|
"efficiency_improvement": (EventClass.RECOMMENDATION, EventDomain.PRODUCTION),
|
|
}
|
|
|
|
|
|
def get_event_classification(event_type: str) -> tuple[EventClass, EventDomain]:
|
|
"""
|
|
Get the event_class and event_domain for a given event_type.
|
|
|
|
Args:
|
|
event_type: The specific event type string
|
|
|
|
Returns:
|
|
Tuple of (EventClass, EventDomain)
|
|
|
|
Raises:
|
|
ValueError: If event_type is not recognized
|
|
"""
|
|
if event_type in EVENT_TYPE_TO_CLASS_MAP:
|
|
return EVENT_TYPE_TO_CLASS_MAP[event_type]
|
|
|
|
# Default: treat unknown types as notifications in operations domain
|
|
return (EventClass.NOTIFICATION, EventDomain.OPERATIONS)
|
|
|
|
|
|
def get_redis_channel(tenant_id: str, event_domain: EventDomain, event_class: EventClass) -> str:
|
|
"""
|
|
Get the Redis pub/sub channel name for an event.
|
|
|
|
Pattern: tenant:{tenant_id}:{domain}.{class}
|
|
Examples:
|
|
- tenant:uuid:inventory.alerts
|
|
- tenant:uuid:production.notifications
|
|
- tenant:uuid:recommendations (recommendations not domain-specific)
|
|
|
|
Args:
|
|
tenant_id: Tenant identifier
|
|
event_domain: Event domain
|
|
event_class: Event class
|
|
|
|
Returns:
|
|
Redis channel name
|
|
"""
|
|
if event_class == EventClass.RECOMMENDATION:
|
|
# Recommendations go to a tenant-wide channel
|
|
return f"tenant:{tenant_id}:recommendations"
|
|
|
|
return f"tenant:{tenant_id}:{event_domain.value}.{event_class.value}s"
|
|
|
|
|
|
def get_rabbitmq_routing_key(event_class: EventClass, event_domain: EventDomain, severity: str) -> str:
|
|
"""
|
|
Get the RabbitMQ routing key for an event.
|
|
|
|
Pattern: {event_class}.{event_domain}.{severity}
|
|
Examples:
|
|
- alert.inventory.urgent
|
|
- notification.production.info
|
|
- recommendation.demand.medium
|
|
|
|
Args:
|
|
event_class: Event class
|
|
event_domain: Event domain
|
|
severity: Severity level (urgent, high, medium, low)
|
|
|
|
Returns:
|
|
RabbitMQ routing key
|
|
"""
|
|
return f"{event_class.value}.{event_domain.value}.{severity}"
|