Initial commit - production deployment
This commit is contained in:
173
services/alert_processor/app/enrichment/urgency_analyzer.py
Normal file
173
services/alert_processor/app/enrichment/urgency_analyzer.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Urgency analyzer for alerts.
|
||||
|
||||
Assesses time sensitivity, deadlines, and determines if action can wait.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class UrgencyAnalyzer:
|
||||
"""Analyze urgency from event metadata"""
|
||||
|
||||
def analyze(self, event_type: str, metadata: Dict[str, Any]) -> dict:
|
||||
"""
|
||||
Analyze urgency for an event.
|
||||
|
||||
Returns dict with:
|
||||
- hours_until_consequence: Time until impact occurs
|
||||
- can_wait_until_tomorrow: Boolean
|
||||
- deadline_utc: ISO datetime if deadline exists
|
||||
- peak_hour_relevant: Boolean
|
||||
- hours_pending: Age of alert
|
||||
"""
|
||||
|
||||
urgency = {
|
||||
"hours_until_consequence": 24, # Default: 24 hours
|
||||
"can_wait_until_tomorrow": True,
|
||||
"deadline_utc": None,
|
||||
"peak_hour_relevant": False,
|
||||
"hours_pending": 0
|
||||
}
|
||||
|
||||
# Calculate based on event type
|
||||
if "critical" in event_type or "urgent" in event_type:
|
||||
urgency["hours_until_consequence"] = 2
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
|
||||
elif "production" in event_type:
|
||||
urgency.update(self._analyze_production_urgency(metadata))
|
||||
|
||||
elif "stock" in event_type or "shortage" in event_type:
|
||||
urgency.update(self._analyze_stock_urgency(metadata))
|
||||
|
||||
elif "delivery" in event_type or "overdue" in event_type:
|
||||
urgency.update(self._analyze_delivery_urgency(metadata))
|
||||
|
||||
elif "po_approval" in event_type:
|
||||
urgency.update(self._analyze_po_approval_urgency(metadata))
|
||||
|
||||
# Check for explicit deadlines
|
||||
if "required_delivery_date" in metadata:
|
||||
urgency.update(self._calculate_deadline_urgency(metadata["required_delivery_date"]))
|
||||
|
||||
if "production_date" in metadata:
|
||||
urgency.update(self._calculate_deadline_urgency(metadata["production_date"]))
|
||||
|
||||
if "expected_date" in metadata:
|
||||
urgency.update(self._calculate_deadline_urgency(metadata["expected_date"]))
|
||||
|
||||
return urgency
|
||||
|
||||
def _analyze_production_urgency(self, metadata: Dict[str, Any]) -> dict:
|
||||
"""Analyze urgency for production alerts"""
|
||||
urgency = {}
|
||||
|
||||
delay_minutes = metadata.get("delay_minutes", 0)
|
||||
|
||||
if delay_minutes > 120:
|
||||
urgency["hours_until_consequence"] = 1
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
elif delay_minutes > 60:
|
||||
urgency["hours_until_consequence"] = 4
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
else:
|
||||
urgency["hours_until_consequence"] = 8
|
||||
|
||||
# Production is peak-hour sensitive
|
||||
urgency["peak_hour_relevant"] = True
|
||||
|
||||
return urgency
|
||||
|
||||
def _analyze_stock_urgency(self, metadata: Dict[str, Any]) -> dict:
|
||||
"""Analyze urgency for stock alerts"""
|
||||
urgency = {}
|
||||
|
||||
# Hours until needed
|
||||
if "hours_until" in metadata:
|
||||
urgency["hours_until_consequence"] = metadata["hours_until"]
|
||||
urgency["can_wait_until_tomorrow"] = urgency["hours_until_consequence"] > 24
|
||||
|
||||
# Days until expiry
|
||||
elif "days_until_expiry" in metadata:
|
||||
days = metadata["days_until_expiry"]
|
||||
if days <= 1:
|
||||
urgency["hours_until_consequence"] = days * 24
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
else:
|
||||
urgency["hours_until_consequence"] = days * 24
|
||||
|
||||
return urgency
|
||||
|
||||
def _analyze_delivery_urgency(self, metadata: Dict[str, Any]) -> dict:
|
||||
"""Analyze urgency for delivery alerts"""
|
||||
urgency = {}
|
||||
|
||||
days_overdue = metadata.get("days_overdue", 0)
|
||||
|
||||
if days_overdue > 3:
|
||||
urgency["hours_until_consequence"] = 2
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
elif days_overdue > 1:
|
||||
urgency["hours_until_consequence"] = 8
|
||||
urgency["can_wait_until_tomorrow"] = False
|
||||
|
||||
return urgency
|
||||
|
||||
def _analyze_po_approval_urgency(self, metadata: Dict[str, Any]) -> dict:
|
||||
"""
|
||||
Analyze urgency for PO approval alerts.
|
||||
|
||||
Uses stockout time (when you run out of stock) instead of delivery date
|
||||
to determine true urgency.
|
||||
"""
|
||||
urgency = {}
|
||||
|
||||
# Extract min_depletion_hours from reasoning_data.parameters
|
||||
reasoning_data = metadata.get("reasoning_data", {})
|
||||
parameters = reasoning_data.get("parameters", {})
|
||||
min_depletion_hours = parameters.get("min_depletion_hours")
|
||||
|
||||
if min_depletion_hours is not None:
|
||||
urgency["hours_until_consequence"] = max(0, round(min_depletion_hours, 1))
|
||||
urgency["can_wait_until_tomorrow"] = min_depletion_hours > 24
|
||||
|
||||
# Set deadline_utc to when stock runs out
|
||||
now = datetime.now(timezone.utc)
|
||||
stockout_time = now + timedelta(hours=min_depletion_hours)
|
||||
urgency["deadline_utc"] = stockout_time.isoformat()
|
||||
|
||||
logger.info(
|
||||
"po_approval_urgency_calculated",
|
||||
min_depletion_hours=min_depletion_hours,
|
||||
stockout_deadline=urgency["deadline_utc"],
|
||||
can_wait=urgency["can_wait_until_tomorrow"]
|
||||
)
|
||||
|
||||
return urgency
|
||||
|
||||
def _calculate_deadline_urgency(self, deadline_str: str) -> dict:
|
||||
"""Calculate urgency based on deadline"""
|
||||
try:
|
||||
if isinstance(deadline_str, str):
|
||||
deadline = datetime.fromisoformat(deadline_str.replace('Z', '+00:00'))
|
||||
else:
|
||||
deadline = deadline_str
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
time_until = deadline - now
|
||||
|
||||
hours_until = time_until.total_seconds() / 3600
|
||||
|
||||
return {
|
||||
"deadline_utc": deadline.isoformat(),
|
||||
"hours_until_consequence": max(0, round(hours_until, 1)),
|
||||
"can_wait_until_tomorrow": hours_until > 24
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning("deadline_parse_failed", deadline=deadline_str, error=str(e))
|
||||
return {}
|
||||
Reference in New Issue
Block a user