New alert system and panel de control page
This commit is contained in:
276
shared/schemas/alert_types.py
Normal file
276
shared/schemas/alert_types.py
Normal file
@@ -0,0 +1,276 @@
|
||||
"""
|
||||
Alert Types for Next-Generation Alert System
|
||||
|
||||
Defines enriched alert types that transform passive notifications into actionable guidance.
|
||||
This replaces simple severity-based alerts with context-rich, prioritized, intelligent alerts.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Dict, Any, Optional, List
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Alert Type Classifications
|
||||
# ============================================================
|
||||
|
||||
class AlertTypeClass(str, Enum):
|
||||
"""High-level alert type classifications"""
|
||||
ACTION_NEEDED = "action_needed" # Requires user decision
|
||||
PREVENTED_ISSUE = "prevented_issue" # AI already handled, FYI
|
||||
TREND_WARNING = "trend_warning" # Proactive insight
|
||||
ESCALATION = "escalation" # Time-sensitive with auto-action countdown
|
||||
INFORMATION = "information" # Pure informational
|
||||
|
||||
|
||||
class PriorityLevel(str, Enum):
|
||||
"""Priority levels based on multi-factor scoring"""
|
||||
CRITICAL = "critical" # 90-100: Needs decision in next 2 hours
|
||||
IMPORTANT = "important" # 70-89: Needs decision today
|
||||
STANDARD = "standard" # 50-69: Review when convenient
|
||||
INFO = "info" # 0-49: For awareness
|
||||
|
||||
|
||||
class PlacementHint(str, Enum):
|
||||
"""UI placement hints for where alert should appear"""
|
||||
TOAST = "toast" # Immediate popup notification
|
||||
ACTION_QUEUE = "action_queue" # Dashboard action queue section
|
||||
DASHBOARD_INLINE = "dashboard_inline" # Embedded in relevant dashboard section
|
||||
NOTIFICATION_PANEL = "notification_panel" # Bell icon notification panel
|
||||
EMAIL_DIGEST = "email_digest" # End-of-day email summary
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Smart Action Definitions
|
||||
# ============================================================
|
||||
|
||||
class SmartActionType(str, Enum):
|
||||
"""Types of smart actions users can take"""
|
||||
APPROVE_PO = "approve_po"
|
||||
REJECT_PO = "reject_po"
|
||||
MODIFY_PO = "modify_po"
|
||||
CALL_SUPPLIER = "call_supplier"
|
||||
NAVIGATE = "navigate"
|
||||
ADJUST_PRODUCTION = "adjust_production"
|
||||
START_PRODUCTION_BATCH = "start_production_batch"
|
||||
NOTIFY_CUSTOMER = "notify_customer"
|
||||
CANCEL_AUTO_ACTION = "cancel_auto_action"
|
||||
MARK_DELIVERY_RECEIVED = "mark_delivery_received"
|
||||
COMPLETE_STOCK_RECEIPT = "complete_stock_receipt"
|
||||
OPEN_REASONING = "open_reasoning"
|
||||
SNOOZE = "snooze"
|
||||
DISMISS = "dismiss"
|
||||
MARK_READ = "mark_read"
|
||||
|
||||
|
||||
class SmartAction(BaseModel):
|
||||
"""Smart action button definition"""
|
||||
label: str = Field(..., description="User-facing button label")
|
||||
type: SmartActionType = Field(..., description="Action type for handler routing")
|
||||
variant: str = Field(default="primary", description="UI variant: primary, secondary, tertiary, danger")
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict, description="Action-specific data")
|
||||
disabled: bool = Field(default=False, description="Whether action is disabled")
|
||||
disabled_reason: Optional[str] = Field(None, description="Reason why action is disabled")
|
||||
estimated_time_minutes: Optional[int] = Field(None, description="Estimated time to complete action")
|
||||
consequence: Optional[str] = Field(None, description="What happens if this action is taken")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Context & Enrichment Models
|
||||
# ============================================================
|
||||
|
||||
class OrchestratorContext(BaseModel):
|
||||
"""Context from Daily Orchestrator about recent actions"""
|
||||
already_addressed: bool = Field(..., description="Has AI already addressed this issue?")
|
||||
action_type: Optional[str] = Field(None, description="Type of action taken: PO, batch, adjustment")
|
||||
action_id: Optional[str] = Field(None, description="ID of the PO/batch created")
|
||||
action_status: Optional[str] = Field(None, description="Status: created, pending_approval, completed")
|
||||
delivery_date: Optional[datetime] = Field(None, description="When will solution arrive")
|
||||
reasoning: Optional[Dict[str, Any]] = Field(None, description="Structured reasoning data")
|
||||
estimated_resolution_time: Optional[datetime] = Field(None, description="When issue will be resolved")
|
||||
estimated_savings_eur: Optional[float] = Field(None, description="Estimated savings from preventing this issue")
|
||||
|
||||
|
||||
class BusinessImpact(BaseModel):
|
||||
"""Business impact assessment"""
|
||||
financial_impact_eur: Optional[float] = Field(None, description="Estimated € impact")
|
||||
affected_orders: Optional[int] = Field(None, description="Number of orders affected")
|
||||
affected_customers: Optional[List[str]] = Field(None, description="Customer names affected")
|
||||
production_batches_at_risk: Optional[List[str]] = Field(None, description="Batch IDs at risk")
|
||||
stockout_risk_hours: Optional[float] = Field(None, description="Hours until stockout")
|
||||
waste_risk_kg: Optional[float] = Field(None, description="Kg of waste risk")
|
||||
customer_satisfaction_impact: Optional[str] = Field(None, description="Impact level: high, medium, low")
|
||||
|
||||
|
||||
class UrgencyContext(BaseModel):
|
||||
"""Urgency and timing context"""
|
||||
deadline: Optional[datetime] = Field(None, description="Hard deadline for decision")
|
||||
time_until_consequence_hours: Optional[float] = Field(None, description="Hours until consequence occurs")
|
||||
can_wait_until_tomorrow: bool = Field(default=True, description="Can this wait until tomorrow?")
|
||||
peak_hour_relevant: bool = Field(default=False, description="Is this relevant during peak hours?")
|
||||
auto_action_countdown_seconds: Optional[int] = Field(None, description="Seconds until auto-action triggers")
|
||||
|
||||
|
||||
class UserAgency(BaseModel):
|
||||
"""User's ability to act on this alert"""
|
||||
can_user_fix: bool = Field(..., description="Can the user actually fix this?")
|
||||
requires_external_party: bool = Field(default=False, description="Requires supplier/customer action?")
|
||||
external_party_name: Optional[str] = Field(None, description="Name of external party")
|
||||
external_party_contact: Optional[str] = Field(None, description="Phone/email of external party")
|
||||
blockers: Optional[List[str]] = Field(None, description="Things blocking user from acting")
|
||||
suggested_workaround: Optional[str] = Field(None, description="Alternative solution if blocked")
|
||||
|
||||
|
||||
class TrendContext(BaseModel):
|
||||
"""Trend analysis context"""
|
||||
metric_name: str = Field(..., description="Name of metric trending")
|
||||
current_value: float = Field(..., description="Current value")
|
||||
baseline_value: float = Field(..., description="Baseline/expected value")
|
||||
change_percentage: float = Field(..., description="Percentage change")
|
||||
direction: str = Field(..., description="Direction: increasing, decreasing")
|
||||
significance: str = Field(..., description="Significance: high, medium, low")
|
||||
period_days: int = Field(..., description="Number of days in trend period")
|
||||
possible_causes: Optional[List[str]] = Field(None, description="Potential root causes")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Enriched Alert Model
|
||||
# ============================================================
|
||||
|
||||
class EnrichedAlert(BaseModel):
|
||||
"""
|
||||
Next-generation enriched alert with full context and guidance.
|
||||
This is what gets sent to the frontend after intelligence processing.
|
||||
"""
|
||||
|
||||
# Original Alert Data
|
||||
id: str = Field(..., description="Alert UUID")
|
||||
tenant_id: str = Field(..., description="Tenant UUID")
|
||||
service: str = Field(..., description="Originating service")
|
||||
alert_type: str = Field(..., description="Specific alert type code")
|
||||
title: str = Field(..., description="User-facing title")
|
||||
message: str = Field(..., description="Detailed message")
|
||||
|
||||
# Classification
|
||||
type_class: AlertTypeClass = Field(..., description="High-level classification")
|
||||
priority_level: PriorityLevel = Field(..., description="Priority level")
|
||||
priority_score: int = Field(..., description="Numeric priority score 0-100")
|
||||
|
||||
# Context Enrichment
|
||||
orchestrator_context: Optional[OrchestratorContext] = Field(None, description="AI system context")
|
||||
business_impact: Optional[BusinessImpact] = Field(None, description="Business impact assessment")
|
||||
urgency_context: Optional[UrgencyContext] = Field(None, description="Urgency and timing")
|
||||
user_agency: Optional[UserAgency] = Field(None, description="User's ability to act")
|
||||
trend_context: Optional[TrendContext] = Field(None, description="Trend analysis (if trend warning)")
|
||||
|
||||
# AI Reasoning
|
||||
ai_reasoning_summary: Optional[str] = Field(None, description="Plain language AI reasoning")
|
||||
reasoning_data: Optional[Dict[str, Any]] = Field(None, description="Structured reasoning from orchestrator")
|
||||
confidence_score: Optional[float] = Field(None, description="AI confidence 0-1")
|
||||
|
||||
# Actions
|
||||
actions: List[SmartAction] = Field(default_factory=list, description="Smart action buttons")
|
||||
primary_action: Optional[SmartAction] = Field(None, description="Primary recommended action")
|
||||
|
||||
# UI Placement
|
||||
placement: List[PlacementHint] = Field(default_factory=list, description="Where to show this alert")
|
||||
|
||||
# Grouping
|
||||
group_id: Optional[str] = Field(None, description="Group ID if part of grouped alerts")
|
||||
is_group_summary: bool = Field(default=False, description="Is this a group summary?")
|
||||
grouped_alert_count: Optional[int] = Field(None, description="Number of alerts in group")
|
||||
grouped_alert_ids: Optional[List[str]] = Field(None, description="IDs of grouped alerts")
|
||||
|
||||
# Metadata
|
||||
created_at: datetime = Field(..., description="When alert was created")
|
||||
enriched_at: datetime = Field(..., description="When alert was enriched")
|
||||
alert_metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
||||
|
||||
# Status
|
||||
status: str = Field(default="active", description="Status: active, resolved, acknowledged, snoozed")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Raw Alert Input Model
|
||||
# ============================================================
|
||||
|
||||
class RawAlert(BaseModel):
|
||||
"""
|
||||
Raw alert from originating services (inventory, production, etc.)
|
||||
This is what services send before enrichment.
|
||||
"""
|
||||
tenant_id: str
|
||||
alert_type: str
|
||||
title: str
|
||||
message: str
|
||||
service: str
|
||||
actions: Optional[List[str]] = None # Simple action labels
|
||||
alert_metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
item_type: str = Field(default="alert") # alert or recommendation
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Alert Group Model
|
||||
# ============================================================
|
||||
|
||||
class AlertGroup(BaseModel):
|
||||
"""Grouped alerts for better UX"""
|
||||
group_id: str = Field(..., description="Group UUID")
|
||||
tenant_id: str = Field(..., description="Tenant UUID")
|
||||
group_type: str = Field(..., description="Type of grouping: supplier, service, type")
|
||||
title: str = Field(..., description="Group title")
|
||||
summary: str = Field(..., description="Group summary message")
|
||||
alert_count: int = Field(..., description="Number of alerts in group")
|
||||
alert_ids: List[str] = Field(..., description="Alert UUIDs in group")
|
||||
highest_priority_score: int = Field(..., description="Highest priority in group")
|
||||
created_at: datetime = Field(..., description="When group was created")
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict, description="Group metadata")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Priority Scoring Components
|
||||
# ============================================================
|
||||
|
||||
class PriorityScoreComponents(BaseModel):
|
||||
"""Breakdown of priority score calculation"""
|
||||
business_impact_score: float = Field(..., description="Business impact component 0-100")
|
||||
urgency_score: float = Field(..., description="Urgency component 0-100")
|
||||
user_agency_score: float = Field(..., description="User agency component 0-100")
|
||||
confidence_score: float = Field(..., description="Confidence component 0-100")
|
||||
final_score: int = Field(..., description="Final weighted score 0-100")
|
||||
weights: Dict[str, float] = Field(..., description="Weights used in calculation")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Standard Alert Type Constants
|
||||
# ============================================================
|
||||
|
||||
class AlertTypeConstants:
|
||||
"""Standard alert type string constants"""
|
||||
|
||||
# Inventory alerts
|
||||
LOW_STOCK_WARNING = "low_stock_warning"
|
||||
CRITICAL_STOCK_SHORTAGE = "critical_stock_shortage"
|
||||
EXPIRING_SOON = "expiring_soon"
|
||||
EXPIRED_STOCK = "expired_stock"
|
||||
|
||||
# Production alerts
|
||||
PRODUCTION_DELAY = "production_delay"
|
||||
PRODUCTION_STALLED = "production_stalled"
|
||||
BATCH_AT_RISK = "batch_at_risk"
|
||||
PRODUCTION_BATCH_START = "production_batch_start"
|
||||
|
||||
# Purchase Order alerts
|
||||
PO_APPROVAL_NEEDED = "po_approval_needed"
|
||||
PO_APPROVAL_ESCALATION = "po_approval_escalation"
|
||||
|
||||
# Delivery lifecycle alerts (NEW)
|
||||
DELIVERY_SCHEDULED = "delivery_scheduled"
|
||||
DELIVERY_ARRIVING_SOON = "delivery_arriving_soon"
|
||||
DELIVERY_OVERDUE = "delivery_overdue"
|
||||
STOCK_RECEIPT_INCOMPLETE = "stock_receipt_incomplete"
|
||||
|
||||
# Forecasting alerts
|
||||
DEMAND_SURGE_PREDICTED = "demand_surge_predicted"
|
||||
DEMAND_DROP_PREDICTED = "demand_drop_predicted"
|
||||
343
shared/schemas/event_classification.py
Normal file
343
shared/schemas/event_classification.py
Normal file
@@ -0,0 +1,343 @@
|
||||
"""
|
||||
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}"
|
||||
Reference in New Issue
Block a user