ringkernel_txmon/types/
transaction.rs

1//! Transaction data types.
2
3use bytemuck::{Pod, Zeroable};
4
5/// Transaction type classification.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7#[repr(u8)]
8pub enum TransactionType {
9    /// Wire transfer.
10    #[default]
11    Wire = 0,
12    /// ACH transfer.
13    ACH = 1,
14    /// Check deposit/payment.
15    Check = 2,
16    /// Card transaction.
17    Card = 3,
18    /// Cash transaction.
19    Cash = 4,
20    /// Internal transfer between accounts.
21    Internal = 5,
22}
23
24impl TransactionType {
25    /// Get display name.
26    pub fn name(&self) -> &'static str {
27        match self {
28            TransactionType::Wire => "Wire",
29            TransactionType::ACH => "ACH",
30            TransactionType::Check => "Check",
31            TransactionType::Card => "Card",
32            TransactionType::Cash => "Cash",
33            TransactionType::Internal => "Internal",
34        }
35    }
36
37    /// Convert from u8.
38    pub fn from_u8(v: u8) -> Option<Self> {
39        match v {
40            0 => Some(TransactionType::Wire),
41            1 => Some(TransactionType::ACH),
42            2 => Some(TransactionType::Check),
43            3 => Some(TransactionType::Card),
44            4 => Some(TransactionType::Cash),
45            5 => Some(TransactionType::Internal),
46            _ => None,
47        }
48    }
49}
50
51impl std::fmt::Display for TransactionType {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        write!(f, "{}", self.name())
54    }
55}
56
57/// 128-byte GPU-aligned transaction structure.
58///
59/// Contains all information about a financial transaction for monitoring.
60#[derive(Debug, Clone, Copy, Pod, Zeroable)]
61#[repr(C, align(128))]
62pub struct Transaction {
63    /// Unique transaction identifier.
64    pub transaction_id: u64,
65    /// Source customer identifier.
66    pub customer_id: u64,
67    /// Destination customer identifier (0 for external).
68    pub destination_id: u64,
69    /// Transaction amount in cents.
70    pub amount_cents: u64,
71    /// Destination country code (ISO 3166-1 numeric).
72    pub country_code: u16,
73    /// Transaction type (cast to TransactionType).
74    pub tx_type: u8,
75    /// Currency code (ISO 4217 numeric, e.g., 840 = USD).
76    pub currency: u8,
77    /// Flags (bitmask for various attributes).
78    pub flags: u32,
79    /// Timestamp (Unix epoch milliseconds).
80    pub timestamp: u64,
81    /// Source account hash (for pattern detection).
82    pub source_hash: u32,
83    /// Destination account hash.
84    pub dest_hash: u32,
85    /// Reference number (for linking related transactions).
86    pub reference: u64,
87    /// Reserved for future use.
88    _reserved: [u8; 64],
89}
90
91const _: () = assert!(std::mem::size_of::<Transaction>() == 128);
92
93impl Transaction {
94    /// Create a new transaction.
95    pub fn new(
96        transaction_id: u64,
97        customer_id: u64,
98        amount_cents: u64,
99        country_code: u16,
100        timestamp: u64,
101    ) -> Self {
102        Self {
103            transaction_id,
104            customer_id,
105            destination_id: 0,
106            amount_cents,
107            country_code,
108            tx_type: TransactionType::Wire as u8,
109            currency: 184, // 840 = USD, but we use u8 so we'll use a smaller code
110            flags: 0,
111            timestamp,
112            source_hash: 0,
113            dest_hash: 0,
114            reference: 0,
115            _reserved: [0; 64],
116        }
117    }
118
119    /// Get the transaction type enum.
120    pub fn tx_type(&self) -> TransactionType {
121        TransactionType::from_u8(self.tx_type).unwrap_or_default()
122    }
123
124    /// Format amount as currency string.
125    pub fn format_amount(&self) -> String {
126        let dollars = self.amount_cents / 100;
127        let cents = self.amount_cents % 100;
128        format!("${}.{:02}", dollars, cents)
129    }
130
131    /// Check if this is a high-value transaction (>$10,000).
132    pub fn is_high_value(&self) -> bool {
133        self.amount_cents >= 1_000_000 // $10,000 in cents
134    }
135
136    /// Check if this is an international transaction.
137    pub fn is_international(&self, home_country: u16) -> bool {
138        self.country_code != home_country
139    }
140
141    /// Get country code as string (simplified mapping).
142    pub fn country_name(&self) -> &'static str {
143        match self.country_code {
144            1 => "US",
145            2 => "UK",
146            3 => "DE",
147            4 => "FR",
148            5 => "JP",
149            6 => "CN",
150            7 => "RU",
151            8 => "BR",
152            9 => "IN",
153            10 => "CA",
154            11 => "AU",
155            12 => "MX",
156            13 => "KR",
157            14 => "IT",
158            15 => "ES",
159            _ => "??",
160        }
161    }
162}
163
164impl Default for Transaction {
165    fn default() -> Self {
166        Self::zeroed()
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_transaction_size() {
176        assert_eq!(std::mem::size_of::<Transaction>(), 128);
177    }
178
179    #[test]
180    fn test_format_amount() {
181        let tx = Transaction::new(1, 1, 123_456, 1, 0);
182        assert_eq!(tx.format_amount(), "$1234.56");
183    }
184
185    #[test]
186    fn test_high_value() {
187        let low = Transaction::new(1, 1, 50_000, 1, 0);
188        let high = Transaction::new(2, 1, 1_500_000, 1, 0);
189
190        assert!(!low.is_high_value());
191        assert!(high.is_high_value());
192    }
193}