Skip to main content

ringkernel_txmon/types/
alert.rs

1//! Alert types and monitoring alert structure.
2
3use bytemuck::Zeroable;
4use iced::Color;
5
6/// Types of compliance alerts that can be triggered during transaction monitoring.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8#[repr(u8)]
9pub enum AlertType {
10    /// Transaction velocity exceeds threshold (too many in time window).
11    #[default]
12    VelocityBreach = 0,
13    /// Single transaction amount exceeds configured threshold.
14    AmountThreshold = 1,
15    /// Geographic anomaly (unusual destination country).
16    GeographicAnomaly = 2,
17    /// Structured transactions to avoid reporting (smurfing).
18    StructuredTransaction = 3,
19    /// Circular trading pattern detected.
20    CircularTrading = 4,
21    /// Entity matched against sanctions list.
22    SanctionsHit = 5,
23    /// Politically Exposed Person related transaction.
24    PEPRelated = 6,
25    /// Adverse media mention found.
26    AdverseMedia = 7,
27    /// Unusual pattern detected (generic).
28    UnusualPattern = 8,
29}
30
31impl AlertType {
32    /// Get display name for the alert type.
33    pub fn name(&self) -> &'static str {
34        match self {
35            AlertType::VelocityBreach => "Velocity Breach",
36            AlertType::AmountThreshold => "Amount Threshold",
37            AlertType::GeographicAnomaly => "Geographic Anomaly",
38            AlertType::StructuredTransaction => "Structured Transaction",
39            AlertType::CircularTrading => "Circular Trading",
40            AlertType::SanctionsHit => "Sanctions Hit",
41            AlertType::PEPRelated => "PEP Related",
42            AlertType::AdverseMedia => "Adverse Media",
43            AlertType::UnusualPattern => "Unusual Pattern",
44        }
45    }
46
47    /// Get short code for display.
48    pub fn code(&self) -> &'static str {
49        match self {
50            AlertType::VelocityBreach => "VEL",
51            AlertType::AmountThreshold => "AMT",
52            AlertType::GeographicAnomaly => "GEO",
53            AlertType::StructuredTransaction => "STR",
54            AlertType::CircularTrading => "CIR",
55            AlertType::SanctionsHit => "SAN",
56            AlertType::PEPRelated => "PEP",
57            AlertType::AdverseMedia => "ADV",
58            AlertType::UnusualPattern => "UNS",
59        }
60    }
61
62    /// Convert from u8.
63    pub fn from_u8(v: u8) -> Option<Self> {
64        match v {
65            0 => Some(AlertType::VelocityBreach),
66            1 => Some(AlertType::AmountThreshold),
67            2 => Some(AlertType::GeographicAnomaly),
68            3 => Some(AlertType::StructuredTransaction),
69            4 => Some(AlertType::CircularTrading),
70            5 => Some(AlertType::SanctionsHit),
71            6 => Some(AlertType::PEPRelated),
72            7 => Some(AlertType::AdverseMedia),
73            8 => Some(AlertType::UnusualPattern),
74            _ => None,
75        }
76    }
77}
78
79impl std::fmt::Display for AlertType {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.name())
82    }
83}
84
85/// Severity level of a compliance alert.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
87#[repr(u8)]
88pub enum AlertSeverity {
89    /// Low severity - informational, minimal risk.
90    #[default]
91    Low = 0,
92    /// Medium severity - requires review but not urgent.
93    Medium = 1,
94    /// High severity - requires prompt attention.
95    High = 2,
96    /// Critical severity - immediate action required.
97    Critical = 3,
98}
99
100impl AlertSeverity {
101    /// Get display name.
102    pub fn name(&self) -> &'static str {
103        match self {
104            AlertSeverity::Low => "LOW",
105            AlertSeverity::Medium => "MEDIUM",
106            AlertSeverity::High => "HIGH",
107            AlertSeverity::Critical => "CRITICAL",
108        }
109    }
110
111    /// Get color for GUI display.
112    pub fn color(&self) -> Color {
113        match self {
114            AlertSeverity::Low => Color::from_rgb(0.4, 0.7, 0.4), // Green
115            AlertSeverity::Medium => Color::from_rgb(0.9, 0.8, 0.2), // Yellow
116            AlertSeverity::High => Color::from_rgb(0.9, 0.5, 0.1), // Orange
117            AlertSeverity::Critical => Color::from_rgb(0.9, 0.2, 0.2), // Red
118        }
119    }
120
121    /// Get indicator character for compact display.
122    pub fn indicator(&self) -> char {
123        match self {
124            AlertSeverity::Low => '\u{25CB}',      // ○
125            AlertSeverity::Medium => '\u{25D0}',   // ◐
126            AlertSeverity::High => '\u{25CF}',     // ●
127            AlertSeverity::Critical => '\u{25C9}', // ◉
128        }
129    }
130
131    /// Convert from u8.
132    pub fn from_u8(v: u8) -> Option<Self> {
133        match v {
134            0 => Some(AlertSeverity::Low),
135            1 => Some(AlertSeverity::Medium),
136            2 => Some(AlertSeverity::High),
137            3 => Some(AlertSeverity::Critical),
138            _ => None,
139        }
140    }
141}
142
143impl std::fmt::Display for AlertSeverity {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(f, "{}", self.name())
146    }
147}
148
149/// Alert workflow status.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
151#[repr(u8)]
152pub enum AlertStatus {
153    /// Newly created alert.
154    #[default]
155    New = 0,
156    /// Alert has been acknowledged.
157    Acknowledged = 1,
158    /// Alert is under review by analyst.
159    UnderReview = 2,
160    /// Alert has been escalated.
161    Escalated = 3,
162    /// Alert has been closed.
163    Closed = 4,
164}
165
166impl AlertStatus {
167    /// Convert from u8.
168    pub fn from_u8(v: u8) -> Option<Self> {
169        match v {
170            0 => Some(AlertStatus::New),
171            1 => Some(AlertStatus::Acknowledged),
172            2 => Some(AlertStatus::UnderReview),
173            3 => Some(AlertStatus::Escalated),
174            4 => Some(AlertStatus::Closed),
175            _ => None,
176        }
177    }
178}
179
180/// 128-byte GPU-aligned monitoring alert structure.
181///
182/// Contains all information about a triggered compliance alert.
183#[derive(Debug, Clone, Copy)]
184#[repr(C, align(128))]
185pub struct MonitoringAlert {
186    /// Unique alert identifier.
187    pub alert_id: u64, // 8 bytes, offset 0
188    /// Related transaction identifier.
189    pub transaction_id: u64, // 8 bytes, offset 8
190    /// Customer identifier who triggered the alert.
191    pub customer_id: u64, // 8 bytes, offset 16
192    /// Alert type (cast to AlertType).
193    pub alert_type: u8, // 1 byte, offset 24
194    /// Alert severity (cast to AlertSeverity).
195    pub severity: u8, // 1 byte, offset 25
196    /// Alert status (cast to AlertStatus).
197    pub status: u8, // 1 byte, offset 26
198    /// Padding bytes.
199    _padding1: [u8; 5], // 5 bytes, offset 27-31
200    /// Transaction amount that triggered alert (cents).
201    pub amount_cents: u64, // 8 bytes, offset 32
202    /// Computed risk score for this alert (0-100).
203    pub risk_score: u32, // 4 bytes, offset 40
204    /// Velocity count at time of alert.
205    pub velocity_count: u32, // 4 bytes, offset 44
206    /// Timestamp when alert was raised (Unix epoch ms).
207    pub timestamp: u64, // 8 bytes, offset 48
208    /// Destination country code (if relevant).
209    pub country_code: u16, // 2 bytes, offset 56
210    /// Additional flags (bitmask).
211    pub flags: u16, // 2 bytes, offset 58
212    /// Padding for alignment.
213    _padding2: [u8; 4], // 4 bytes, offset 60-63
214    /// Reserved for future use.
215    _reserved: [u8; 64], // 64 bytes, offset 64-127
216}
217
218// Manual implementations since we can't derive Pod due to padding sensitivity
219unsafe impl bytemuck::Zeroable for MonitoringAlert {}
220unsafe impl bytemuck::Pod for MonitoringAlert {}
221
222const _: () = assert!(std::mem::size_of::<MonitoringAlert>() == 128);
223
224impl MonitoringAlert {
225    /// Create a new monitoring alert.
226    pub fn new(
227        alert_id: u64,
228        transaction_id: u64,
229        customer_id: u64,
230        alert_type: AlertType,
231        severity: AlertSeverity,
232        amount_cents: u64,
233        timestamp: u64,
234    ) -> Self {
235        Self {
236            alert_id,
237            transaction_id,
238            customer_id,
239            alert_type: alert_type as u8,
240            severity: severity as u8,
241            status: AlertStatus::New as u8,
242            _padding1: [0; 5],
243            amount_cents,
244            risk_score: 0,
245            velocity_count: 0,
246            timestamp,
247            country_code: 0,
248            flags: 0,
249            _padding2: [0; 4],
250            _reserved: [0; 64],
251        }
252    }
253
254    /// Get the alert type enum.
255    pub fn alert_type(&self) -> AlertType {
256        AlertType::from_u8(self.alert_type).unwrap_or_default()
257    }
258
259    /// Get the severity enum.
260    pub fn severity(&self) -> AlertSeverity {
261        AlertSeverity::from_u8(self.severity).unwrap_or_default()
262    }
263
264    /// Get the status enum.
265    pub fn status(&self) -> AlertStatus {
266        AlertStatus::from_u8(self.status).unwrap_or_default()
267    }
268
269    /// Format amount as currency string.
270    pub fn format_amount(&self) -> String {
271        let dollars = self.amount_cents / 100;
272        let cents = self.amount_cents % 100;
273        format!("${}.{:02}", dollars, cents)
274    }
275
276    /// Check if this alert requires immediate attention.
277    pub fn requires_immediate_action(&self) -> bool {
278        self.severity() == AlertSeverity::Critical
279            || self.alert_type() == AlertType::SanctionsHit
280            || self.alert_type() == AlertType::PEPRelated
281    }
282}
283
284impl Default for MonitoringAlert {
285    fn default() -> Self {
286        Self::zeroed()
287    }
288}