ringkernel_txmon/types/
customer.rs1use bytemuck::Zeroable;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
7#[repr(u8)]
8pub enum CustomerRiskLevel {
9 #[default]
11 Low = 0,
12 Medium = 1,
14 High = 2,
16 Prohibited = 3,
18}
19
20impl CustomerRiskLevel {
21 pub fn name(&self) -> &'static str {
23 match self {
24 CustomerRiskLevel::Low => "Low",
25 CustomerRiskLevel::Medium => "Medium",
26 CustomerRiskLevel::High => "High",
27 CustomerRiskLevel::Prohibited => "Prohibited",
28 }
29 }
30
31 pub fn from_u8(v: u8) -> Option<Self> {
33 match v {
34 0 => Some(CustomerRiskLevel::Low),
35 1 => Some(CustomerRiskLevel::Medium),
36 2 => Some(CustomerRiskLevel::High),
37 3 => Some(CustomerRiskLevel::Prohibited),
38 _ => None,
39 }
40 }
41}
42
43impl std::fmt::Display for CustomerRiskLevel {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{}", self.name())
46 }
47}
48
49#[derive(Debug, Clone, Copy)]
53#[repr(C, align(128))]
54pub struct CustomerRiskProfile {
55 pub customer_id: u64, pub risk_level: u8, pub risk_score: u8, pub country_code: u16, pub is_pep: u8, pub requires_edd: u8, pub has_adverse_media: u8, pub geographic_risk: u8, pub business_risk: u8, pub behavioral_risk: u8, _padding1: [u8; 2], pub transaction_count: u32, pub alert_count: u32, pub velocity_count: u32, pub amount_threshold: u64, pub velocity_threshold: u32, _padding2: u32, pub allowed_destinations: u64, pub avg_monthly_volume: u64, pub last_transaction_ts: u64, pub created_ts: u64, _reserved: [u8; 48], }
100
101unsafe impl bytemuck::Zeroable for CustomerRiskProfile {}
103unsafe impl bytemuck::Pod for CustomerRiskProfile {}
104
105const _: () = assert!(std::mem::size_of::<CustomerRiskProfile>() == 128);
106
107impl CustomerRiskProfile {
108 pub fn new(customer_id: u64, country_code: u16) -> Self {
110 Self {
111 customer_id,
112 risk_level: CustomerRiskLevel::Low as u8,
113 risk_score: 10,
114 country_code,
115 is_pep: 0,
116 requires_edd: 0,
117 has_adverse_media: 0,
118 geographic_risk: 10,
119 business_risk: 10,
120 behavioral_risk: 10,
121 _padding1: [0; 2],
122 transaction_count: 0,
123 alert_count: 0,
124 velocity_count: 0,
125 amount_threshold: 0,
126 velocity_threshold: 0,
127 _padding2: 0,
128 allowed_destinations: !0, avg_monthly_volume: 0,
130 last_transaction_ts: 0,
131 created_ts: 0,
132 _reserved: [0; 48],
133 }
134 }
135
136 pub fn risk_level(&self) -> CustomerRiskLevel {
138 CustomerRiskLevel::from_u8(self.risk_level).unwrap_or_default()
139 }
140
141 pub fn is_pep(&self) -> bool {
143 self.is_pep != 0
144 }
145
146 pub fn requires_edd(&self) -> bool {
148 self.requires_edd != 0
149 }
150
151 pub fn has_adverse_media(&self) -> bool {
153 self.has_adverse_media != 0
154 }
155
156 pub fn is_high_risk(&self) -> bool {
158 self.risk_level() >= CustomerRiskLevel::High || self.risk_score >= 70
159 }
160
161 pub fn is_destination_allowed(&self, country_code: u16) -> bool {
163 if country_code >= 64 {
164 true
166 } else {
167 (self.allowed_destinations >> country_code) & 1 == 1
168 }
169 }
170
171 pub fn increment_velocity(&mut self) {
173 self.velocity_count = self.velocity_count.saturating_add(1);
174 }
175
176 pub fn reset_velocity(&mut self) {
178 self.velocity_count = 0;
179 }
180
181 pub fn increment_transactions(&mut self) {
183 self.transaction_count = self.transaction_count.saturating_add(1);
184 }
185
186 pub fn increment_alerts(&mut self) {
188 self.alert_count = self.alert_count.saturating_add(1);
189 }
190}
191
192impl Default for CustomerRiskProfile {
193 fn default() -> Self {
194 Self::zeroed()
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_profile_size() {
204 assert_eq!(std::mem::size_of::<CustomerRiskProfile>(), 128);
205 }
206
207 #[test]
208 fn test_destination_allowed() {
209 let mut profile = CustomerRiskProfile::new(1, 1);
210 profile.allowed_destinations = 0b1111; assert!(profile.is_destination_allowed(0));
213 assert!(profile.is_destination_allowed(1));
214 assert!(profile.is_destination_allowed(2));
215 assert!(profile.is_destination_allowed(3));
216 assert!(!profile.is_destination_allowed(4));
217 assert!(!profile.is_destination_allowed(10));
218 assert!(profile.is_destination_allowed(100));
220 }
221}