New alert service
This commit is contained in:
@@ -1,219 +0,0 @@
|
||||
/**
|
||||
* Next-Generation Alert Types
|
||||
*
|
||||
* TypeScript definitions for enriched, context-aware alerts
|
||||
* Matches shared/schemas/alert_types.py
|
||||
*/
|
||||
|
||||
export enum AlertTypeClass {
|
||||
ACTION_NEEDED = 'action_needed',
|
||||
PREVENTED_ISSUE = 'prevented_issue',
|
||||
TREND_WARNING = 'trend_warning',
|
||||
ESCALATION = 'escalation',
|
||||
INFORMATION = 'information'
|
||||
}
|
||||
|
||||
export enum PriorityLevel {
|
||||
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
|
||||
}
|
||||
|
||||
export enum PlacementHint {
|
||||
TOAST = 'toast',
|
||||
ACTION_QUEUE = 'action_queue',
|
||||
DASHBOARD_INLINE = 'dashboard_inline',
|
||||
NOTIFICATION_PANEL = 'notification_panel',
|
||||
EMAIL_DIGEST = 'email_digest'
|
||||
}
|
||||
|
||||
export enum SmartActionType {
|
||||
APPROVE_PO = 'approve_po',
|
||||
REJECT_PO = 'reject_po',
|
||||
CALL_SUPPLIER = 'call_supplier',
|
||||
NAVIGATE = 'navigate',
|
||||
ADJUST_PRODUCTION = 'adjust_production',
|
||||
NOTIFY_CUSTOMER = 'notify_customer',
|
||||
CANCEL_AUTO_ACTION = 'cancel_auto_action',
|
||||
OPEN_REASONING = 'open_reasoning',
|
||||
SNOOZE = 'snooze',
|
||||
DISMISS = 'dismiss',
|
||||
MARK_READ = 'mark_read'
|
||||
}
|
||||
|
||||
export interface SmartAction {
|
||||
label: string;
|
||||
type: SmartActionType;
|
||||
variant: 'primary' | 'secondary' | 'tertiary' | 'danger';
|
||||
metadata: Record<string, any>;
|
||||
disabled?: boolean;
|
||||
disabled_reason?: string;
|
||||
estimated_time_minutes?: number;
|
||||
consequence?: string;
|
||||
}
|
||||
|
||||
export interface OrchestratorContext {
|
||||
already_addressed: boolean;
|
||||
action_type?: string;
|
||||
action_id?: string;
|
||||
action_status?: string;
|
||||
delivery_date?: string;
|
||||
reasoning?: Record<string, any>;
|
||||
estimated_resolution_time?: string;
|
||||
}
|
||||
|
||||
export interface BusinessImpact {
|
||||
financial_impact_eur?: number;
|
||||
affected_orders?: number;
|
||||
affected_customers?: string[];
|
||||
production_batches_at_risk?: string[];
|
||||
stockout_risk_hours?: number;
|
||||
waste_risk_kg?: number;
|
||||
customer_satisfaction_impact?: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
export interface UrgencyContext {
|
||||
deadline?: string;
|
||||
time_until_consequence_hours?: number;
|
||||
can_wait_until_tomorrow: boolean;
|
||||
peak_hour_relevant: boolean;
|
||||
auto_action_countdown_seconds?: number;
|
||||
}
|
||||
|
||||
export interface UserAgency {
|
||||
can_user_fix: boolean;
|
||||
requires_external_party: boolean;
|
||||
external_party_name?: string;
|
||||
external_party_contact?: string;
|
||||
blockers?: string[];
|
||||
suggested_workaround?: string;
|
||||
}
|
||||
|
||||
export interface TrendContext {
|
||||
metric_name: string;
|
||||
current_value: number;
|
||||
baseline_value: number;
|
||||
change_percentage: number;
|
||||
direction: 'increasing' | 'decreasing';
|
||||
significance: 'high' | 'medium' | 'low';
|
||||
period_days: number;
|
||||
possible_causes?: string[];
|
||||
}
|
||||
|
||||
export interface EnrichedAlert {
|
||||
// Original Alert Data
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
service: string;
|
||||
alert_type: string;
|
||||
title: string;
|
||||
message: string;
|
||||
|
||||
// Classification
|
||||
type_class: AlertTypeClass;
|
||||
priority_level: PriorityLevel;
|
||||
priority_score: number;
|
||||
|
||||
// Context Enrichment
|
||||
orchestrator_context?: OrchestratorContext;
|
||||
business_impact?: BusinessImpact;
|
||||
urgency_context?: UrgencyContext;
|
||||
user_agency?: UserAgency;
|
||||
trend_context?: TrendContext;
|
||||
|
||||
// AI Reasoning
|
||||
ai_reasoning_summary?: string;
|
||||
reasoning_data?: Record<string, any>;
|
||||
confidence_score?: number;
|
||||
|
||||
// Actions
|
||||
actions: SmartAction[];
|
||||
primary_action?: SmartAction;
|
||||
|
||||
// UI Placement
|
||||
placement: PlacementHint[];
|
||||
|
||||
// Grouping
|
||||
group_id?: string;
|
||||
is_group_summary: boolean;
|
||||
grouped_alert_count?: number;
|
||||
grouped_alert_ids?: string[];
|
||||
|
||||
// Metadata
|
||||
created_at: string;
|
||||
enriched_at: string;
|
||||
alert_metadata: Record<string, any>;
|
||||
status: 'active' | 'resolved' | 'acknowledged' | 'snoozed';
|
||||
}
|
||||
|
||||
export interface PriorityScoreComponents {
|
||||
business_impact_score: number;
|
||||
urgency_score: number;
|
||||
user_agency_score: number;
|
||||
confidence_score: number;
|
||||
final_score: number;
|
||||
weights: Record<string, number>;
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
export function getPriorityColor(level: PriorityLevel): string {
|
||||
switch (level) {
|
||||
case PriorityLevel.CRITICAL:
|
||||
return 'var(--color-error)';
|
||||
case PriorityLevel.IMPORTANT:
|
||||
return 'var(--color-warning)';
|
||||
case PriorityLevel.STANDARD:
|
||||
return 'var(--color-info)';
|
||||
case PriorityLevel.INFO:
|
||||
return 'var(--color-success)';
|
||||
}
|
||||
}
|
||||
|
||||
export function getPriorityIcon(level: PriorityLevel): string {
|
||||
switch (level) {
|
||||
case PriorityLevel.CRITICAL:
|
||||
return 'alert-triangle';
|
||||
case PriorityLevel.IMPORTANT:
|
||||
return 'alert-circle';
|
||||
case PriorityLevel.STANDARD:
|
||||
return 'info';
|
||||
case PriorityLevel.INFO:
|
||||
return 'check-circle';
|
||||
}
|
||||
}
|
||||
|
||||
export function getTypeClassBadgeVariant(typeClass: AlertTypeClass): 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'outline' {
|
||||
switch (typeClass) {
|
||||
case AlertTypeClass.ACTION_NEEDED:
|
||||
return 'error';
|
||||
case AlertTypeClass.PREVENTED_ISSUE:
|
||||
return 'success';
|
||||
case AlertTypeClass.TREND_WARNING:
|
||||
return 'warning';
|
||||
case AlertTypeClass.ESCALATION:
|
||||
return 'error';
|
||||
case AlertTypeClass.INFORMATION:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
export function formatTimeUntilConsequence(hours?: number): string {
|
||||
if (!hours) return '';
|
||||
|
||||
if (hours < 1) {
|
||||
return `${Math.round(hours * 60)} minutes`;
|
||||
} else if (hours < 24) {
|
||||
return `${Math.round(hours)} hours`;
|
||||
} else {
|
||||
return `${Math.round(hours / 24)} days`;
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldShowToast(alert: EnrichedAlert): boolean {
|
||||
return alert.placement.includes(PlacementHint.TOAST);
|
||||
}
|
||||
|
||||
export function shouldShowInActionQueue(alert: EnrichedAlert): boolean {
|
||||
return alert.placement.includes(PlacementHint.ACTION_QUEUE);
|
||||
}
|
||||
@@ -1,369 +0,0 @@
|
||||
/**
|
||||
* Event System Type Definitions
|
||||
*
|
||||
* Matches backend event architecture with three-tier model:
|
||||
* - ALERT: Actionable events requiring user decision
|
||||
* - NOTIFICATION: Informational state changes
|
||||
* - RECOMMENDATION: AI-generated suggestions
|
||||
*/
|
||||
|
||||
// ============================================================
|
||||
// Event Classifications
|
||||
// ============================================================
|
||||
|
||||
export type EventClass = 'alert' | 'notification' | 'recommendation';
|
||||
|
||||
export type EventDomain =
|
||||
| 'inventory'
|
||||
| 'production'
|
||||
| 'supply_chain'
|
||||
| 'demand'
|
||||
| 'operations';
|
||||
|
||||
export type PriorityLevel = 'critical' | 'important' | 'standard' | 'info';
|
||||
|
||||
export type AlertTypeClass =
|
||||
| 'action_needed'
|
||||
| 'prevented_issue'
|
||||
| 'trend_warning'
|
||||
| 'escalation'
|
||||
| 'information';
|
||||
|
||||
export type NotificationType =
|
||||
| 'state_change'
|
||||
| 'completion'
|
||||
| 'arrival'
|
||||
| 'departure'
|
||||
| 'update'
|
||||
| 'system_event';
|
||||
|
||||
export type RecommendationType =
|
||||
| 'optimization'
|
||||
| 'cost_reduction'
|
||||
| 'risk_mitigation'
|
||||
| 'trend_insight'
|
||||
| 'best_practice';
|
||||
|
||||
// ============================================================
|
||||
// Base Event Interface
|
||||
// ============================================================
|
||||
|
||||
export interface BaseEvent {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
event_class: EventClass;
|
||||
event_domain: EventDomain;
|
||||
event_type: string;
|
||||
service: string;
|
||||
title: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
created_at: string;
|
||||
metadata?: Record<string, any>;
|
||||
_channel?: string; // Added by gateway for frontend routing
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Alert (Full Enrichment)
|
||||
// ============================================================
|
||||
|
||||
export interface OrchestratorContext {
|
||||
already_addressed?: boolean;
|
||||
action_type?: string;
|
||||
action_id?: string;
|
||||
action_status?: string;
|
||||
delivery_date?: string;
|
||||
reasoning?: string;
|
||||
}
|
||||
|
||||
export interface BusinessImpact {
|
||||
financial_impact_eur?: number;
|
||||
affected_orders?: number;
|
||||
affected_customers?: string[];
|
||||
production_batches_at_risk?: string[];
|
||||
stockout_risk_hours?: number;
|
||||
waste_risk_kg?: number;
|
||||
customer_satisfaction_impact?: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
export interface UrgencyContext {
|
||||
deadline?: string;
|
||||
time_until_consequence_hours?: number;
|
||||
can_wait_until_tomorrow?: boolean;
|
||||
peak_hour_relevant?: boolean;
|
||||
auto_action_countdown_seconds?: number;
|
||||
}
|
||||
|
||||
export interface UserAgency {
|
||||
can_user_fix?: boolean;
|
||||
requires_external_party?: boolean;
|
||||
external_party_name?: string;
|
||||
external_party_contact?: string;
|
||||
blockers?: string[];
|
||||
suggested_workaround?: string;
|
||||
}
|
||||
|
||||
export interface SmartAction {
|
||||
type: string;
|
||||
label: string;
|
||||
variant?: 'primary' | 'secondary' | 'danger' | 'success';
|
||||
metadata?: Record<string, any>;
|
||||
estimated_time_minutes?: number;
|
||||
consequence?: string;
|
||||
disabled?: boolean;
|
||||
disabled_reason?: string;
|
||||
}
|
||||
|
||||
export interface Alert extends BaseEvent {
|
||||
event_class: 'alert';
|
||||
type_class: AlertTypeClass;
|
||||
status: 'active' | 'acknowledged' | 'resolved' | 'dismissed' | 'in_progress';
|
||||
|
||||
// Priority
|
||||
priority_score: number; // 0-100
|
||||
priority_level: PriorityLevel;
|
||||
|
||||
// Enrichment context
|
||||
orchestrator_context?: OrchestratorContext;
|
||||
business_impact?: BusinessImpact;
|
||||
urgency_context?: UrgencyContext;
|
||||
user_agency?: UserAgency;
|
||||
trend_context?: Record<string, any>;
|
||||
|
||||
// Smart actions
|
||||
actions?: SmartAction[];
|
||||
|
||||
// AI reasoning
|
||||
ai_reasoning_summary?: string;
|
||||
confidence_score?: number;
|
||||
|
||||
// Timing
|
||||
timing_decision?: 'send_now' | 'schedule_later' | 'batch_for_digest';
|
||||
scheduled_send_time?: string;
|
||||
placement?: string[];
|
||||
|
||||
// Escalation & chaining
|
||||
action_created_at?: string;
|
||||
superseded_by_action_id?: string;
|
||||
hidden_from_ui?: boolean;
|
||||
|
||||
// Timestamps
|
||||
updated_at?: string;
|
||||
resolved_at?: string;
|
||||
|
||||
// Legacy fields (for backward compatibility)
|
||||
alert_type?: string;
|
||||
item_type?: 'alert';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Notification (Lightweight)
|
||||
// ============================================================
|
||||
|
||||
export interface Notification extends BaseEvent {
|
||||
event_class: 'notification';
|
||||
notification_type: NotificationType;
|
||||
|
||||
// Entity context
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
old_state?: string;
|
||||
new_state?: string;
|
||||
|
||||
// Display
|
||||
placement?: string[];
|
||||
|
||||
// TTL
|
||||
expires_at?: string;
|
||||
|
||||
// Legacy fields
|
||||
item_type?: 'notification';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Recommendation (AI Suggestions)
|
||||
// ============================================================
|
||||
|
||||
export interface Recommendation extends BaseEvent {
|
||||
event_class: 'recommendation';
|
||||
recommendation_type: RecommendationType;
|
||||
|
||||
// Light priority
|
||||
priority_level: PriorityLevel;
|
||||
|
||||
// Context
|
||||
estimated_impact?: {
|
||||
financial_savings_eur?: number;
|
||||
time_saved_hours?: number;
|
||||
efficiency_gain_percent?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
suggested_actions?: SmartAction[];
|
||||
|
||||
// AI reasoning
|
||||
ai_reasoning_summary?: string;
|
||||
confidence_score?: number;
|
||||
|
||||
// Dismissal
|
||||
dismissed_at?: string;
|
||||
dismissed_by?: string;
|
||||
|
||||
// Timestamps
|
||||
updated_at?: string;
|
||||
|
||||
// Legacy fields
|
||||
item_type?: 'recommendation';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Union Types
|
||||
// ============================================================
|
||||
|
||||
export type Event = Alert | Notification | Recommendation;
|
||||
|
||||
// Type guards
|
||||
export function isAlert(event: Event): event is Alert {
|
||||
return event.event_class === 'alert' || event.item_type === 'alert';
|
||||
}
|
||||
|
||||
export function isNotification(event: Event): event is Notification {
|
||||
return event.event_class === 'notification';
|
||||
}
|
||||
|
||||
export function isRecommendation(event: Event): event is Recommendation {
|
||||
return event.event_class === 'recommendation' || event.item_type === 'recommendation';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Channel Patterns
|
||||
// ============================================================
|
||||
|
||||
export type ChannelPattern =
|
||||
| `${EventDomain}.${Exclude<EventClass, 'recommendation'>}` // e.g., "inventory.alerts"
|
||||
| `${EventDomain}.*` // e.g., "inventory.*"
|
||||
| `*.${Exclude<EventClass, 'recommendation'>}` // e.g., "*.alerts"
|
||||
| 'recommendations'
|
||||
| '*.*';
|
||||
|
||||
// ============================================================
|
||||
// Hook Configuration Types
|
||||
// ============================================================
|
||||
|
||||
export interface UseAlertsConfig {
|
||||
domains?: EventDomain[];
|
||||
minPriority?: PriorityLevel;
|
||||
typeClass?: AlertTypeClass[];
|
||||
includeResolved?: boolean;
|
||||
maxAge?: number; // seconds
|
||||
}
|
||||
|
||||
export interface UseNotificationsConfig {
|
||||
domains?: EventDomain[];
|
||||
eventTypes?: string[];
|
||||
maxAge?: number; // seconds, default 3600 (1 hour)
|
||||
}
|
||||
|
||||
export interface UseRecommendationsConfig {
|
||||
domains?: EventDomain[];
|
||||
includeDismissed?: boolean;
|
||||
minConfidence?: number; // 0.0 - 1.0
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SSE Event Types
|
||||
// ============================================================
|
||||
|
||||
export interface SSEConnectionEvent {
|
||||
type: 'connected';
|
||||
message: string;
|
||||
channels: string[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface SSEHeartbeatEvent {
|
||||
type: 'heartbeat';
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface SSEInitialStateEvent {
|
||||
events: Event[];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Backward Compatibility (Legacy Alert Format)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* @deprecated Use Alert type instead
|
||||
*/
|
||||
export interface LegacyAlert {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
item_type: 'alert' | 'recommendation';
|
||||
alert_type: string;
|
||||
service: string;
|
||||
title: string;
|
||||
message: string;
|
||||
priority_level?: string;
|
||||
priority_score?: number;
|
||||
type_class?: string;
|
||||
status?: string;
|
||||
actions?: any[];
|
||||
metadata?: Record<string, any>;
|
||||
timestamp: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert legacy alert format to new Event format
|
||||
*/
|
||||
export function convertLegacyAlert(legacy: LegacyAlert): Event {
|
||||
const eventClass: EventClass = legacy.item_type === 'recommendation' ? 'recommendation' : 'alert';
|
||||
|
||||
// Infer domain from service (best effort)
|
||||
const domainMap: Record<string, EventDomain> = {
|
||||
'inventory': 'inventory',
|
||||
'production': 'production',
|
||||
'procurement': 'supply_chain',
|
||||
'forecasting': 'demand',
|
||||
'orchestrator': 'operations',
|
||||
};
|
||||
const event_domain = domainMap[legacy.service] || 'operations';
|
||||
|
||||
const base = {
|
||||
id: legacy.id,
|
||||
tenant_id: legacy.tenant_id,
|
||||
event_class: eventClass,
|
||||
event_domain,
|
||||
event_type: legacy.alert_type,
|
||||
service: legacy.service,
|
||||
title: legacy.title,
|
||||
message: legacy.message,
|
||||
timestamp: legacy.timestamp,
|
||||
created_at: legacy.created_at,
|
||||
metadata: legacy.metadata,
|
||||
};
|
||||
|
||||
if (eventClass === 'alert') {
|
||||
return {
|
||||
...base,
|
||||
event_class: 'alert',
|
||||
type_class: (legacy.type_class as AlertTypeClass) || 'action_needed',
|
||||
status: (legacy.status as any) || 'active',
|
||||
priority_score: legacy.priority_score || 50,
|
||||
priority_level: (legacy.priority_level as PriorityLevel) || 'standard',
|
||||
actions: legacy.actions as SmartAction[],
|
||||
alert_type: legacy.alert_type,
|
||||
item_type: 'alert',
|
||||
} as Alert;
|
||||
} else {
|
||||
return {
|
||||
...base,
|
||||
event_class: 'recommendation',
|
||||
recommendation_type: 'trend_insight',
|
||||
priority_level: (legacy.priority_level as PriorityLevel) || 'info',
|
||||
suggested_actions: legacy.actions as SmartAction[],
|
||||
item_type: 'recommendation',
|
||||
} as Recommendation;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user