ringkernel_txmon/factory/
generator.rs1use super::{AccountGenerator, TransactionPattern};
4use crate::types::{CustomerRiskLevel, CustomerRiskProfile, Transaction};
5use rand::prelude::*;
6use rand::rngs::SmallRng;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum FactoryState {
12 #[default]
14 Stopped,
15 Running,
17 Paused,
19}
20
21impl std::fmt::Display for FactoryState {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 FactoryState::Stopped => write!(f, "Stopped"),
25 FactoryState::Running => write!(f, "Running"),
26 FactoryState::Paused => write!(f, "Paused"),
27 }
28 }
29}
30
31#[derive(Debug, Clone)]
33pub struct GeneratorConfig {
34 pub transactions_per_second: u32,
36 pub customer_count: u32,
38 pub suspicious_rate: u8,
40 pub batch_size: u32,
42}
43
44impl Default for GeneratorConfig {
45 fn default() -> Self {
46 Self {
47 transactions_per_second: 100,
48 customer_count: 1000,
49 suspicious_rate: 5,
50 batch_size: 64,
51 }
52 }
53}
54
55pub struct TransactionGenerator {
57 config: GeneratorConfig,
58 rng: SmallRng,
59 customers: Vec<CustomerRiskProfile>,
60 customer_map: HashMap<u64, usize>,
61 next_tx_id: u64,
62}
63
64impl TransactionGenerator {
65 pub fn new(config: GeneratorConfig) -> Self {
67 let mut acct_gen = AccountGenerator::new(42);
68 let customers = acct_gen.generate_customers(config.customer_count);
69
70 let customer_map: HashMap<u64, usize> = customers
71 .iter()
72 .enumerate()
73 .map(|(i, c)| (c.customer_id, i))
74 .collect();
75
76 Self {
77 config,
78 rng: SmallRng::seed_from_u64(12345),
79 customers,
80 customer_map,
81 next_tx_id: 1,
82 }
83 }
84
85 pub fn config(&self) -> &GeneratorConfig {
87 &self.config
88 }
89
90 pub fn set_config(&mut self, config: GeneratorConfig) {
92 self.config = config;
93 }
94
95 pub fn customer_count(&self) -> usize {
97 self.customers.len()
98 }
99
100 pub fn customers(&self) -> &[CustomerRiskProfile] {
102 &self.customers
103 }
104
105 pub fn get_customer_mut(&mut self, customer_id: u64) -> Option<&mut CustomerRiskProfile> {
107 self.customer_map
108 .get(&customer_id)
109 .copied()
110 .and_then(|idx| self.customers.get_mut(idx))
111 }
112
113 pub fn generate_batch(&mut self) -> (Vec<Transaction>, Vec<CustomerRiskProfile>) {
117 let batch_size = self.config.batch_size as usize;
118 let mut transactions = Vec::with_capacity(batch_size);
119 let mut profiles = Vec::with_capacity(batch_size);
120
121 let timestamp = std::time::SystemTime::now()
122 .duration_since(std::time::UNIX_EPOCH)
123 .unwrap()
124 .as_millis() as u64;
125
126 let mut suspicious_tx_indices = Vec::new();
128
129 for i in 0..batch_size {
130 let customer_idx = self.rng.gen_range(0..self.customers.len());
132 let customer = &self.customers[customer_idx];
133
134 let is_suspicious = self.config.suspicious_rate > 0
136 && self.rng.gen_range(0..100) < self.config.suspicious_rate;
137
138 let pattern = if is_suspicious {
140 TransactionPattern::random_suspicious(&mut self.rng)
141 } else {
142 TransactionPattern::random_normal(&mut self.rng)
143 };
144
145 let tx_id = self.next_tx_id;
147 self.next_tx_id += 1;
148
149 let tx_timestamp = timestamp + i as u64;
151
152 let tx = pattern.generate(tx_id, customer, tx_timestamp, &mut self.rng);
153
154 transactions.push(tx);
155 profiles.push(*customer);
156
157 if is_suspicious {
158 suspicious_tx_indices.push(i);
159 }
160 }
161
162 for &idx in &suspicious_tx_indices {
165 let tx = &transactions[idx];
166 if let Some(customer) = self.get_customer_mut(tx.customer_id) {
167 customer.increment_velocity();
168 }
169 }
170
171 for tx in &transactions {
173 if let Some(customer) = self.get_customer_mut(tx.customer_id) {
174 customer.increment_transactions();
175 customer.last_transaction_ts = tx.timestamp;
176 }
177 }
178
179 (transactions, profiles)
180 }
181
182 pub fn reset_velocity_window(&mut self) {
184 for customer in &mut self.customers {
185 customer.reset_velocity();
186 }
187 }
188
189 pub fn customer_stats(&self) -> CustomerStats {
191 let mut stats = CustomerStats {
192 total: self.customers.len(),
193 ..Default::default()
194 };
195
196 for c in &self.customers {
197 match c.risk_level() {
198 CustomerRiskLevel::Low => stats.low_risk += 1,
199 CustomerRiskLevel::Medium => stats.medium_risk += 1,
200 CustomerRiskLevel::High => stats.high_risk += 1,
201 CustomerRiskLevel::Prohibited => stats.prohibited += 1,
202 }
203 if c.is_pep() {
204 stats.pep += 1;
205 }
206 if c.requires_edd() {
207 stats.edd_required += 1;
208 }
209 }
210
211 stats
212 }
213}
214
215#[derive(Debug, Clone, Default)]
217pub struct CustomerStats {
218 pub total: usize,
220 pub low_risk: usize,
222 pub medium_risk: usize,
224 pub high_risk: usize,
226 pub prohibited: usize,
228 pub pep: usize,
230 pub edd_required: usize,
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_generate_batch() {
240 let config = GeneratorConfig {
241 batch_size: 100,
242 ..Default::default()
243 };
244 let mut gen = TransactionGenerator::new(config);
245
246 let (transactions, profiles) = gen.generate_batch();
247
248 assert_eq!(transactions.len(), 100);
249 assert_eq!(profiles.len(), 100);
250
251 for (tx, profile) in transactions.iter().zip(profiles.iter()) {
253 assert_eq!(tx.customer_id, profile.customer_id);
254 }
255 }
256
257 #[test]
258 fn test_suspicious_rate() {
259 let config = GeneratorConfig {
260 batch_size: 1000,
261 suspicious_rate: 10, ..Default::default()
263 };
264 let mut gen = TransactionGenerator::new(config);
265
266 let (transactions, _) = gen.generate_batch();
267
268 let near_threshold = transactions
270 .iter()
271 .filter(|tx| tx.amount_cents >= 900_000 && tx.amount_cents < 1_000_000)
272 .count();
273
274 assert!(near_threshold > 0);
276 }
277
278 #[test]
279 fn test_velocity_increment() {
280 let config = GeneratorConfig {
281 batch_size: 10,
282 customer_count: 1,
283 suspicious_rate: 100, ..Default::default()
285 };
286 let mut gen = TransactionGenerator::new(config);
287
288 assert_eq!(gen.customers[0].velocity_count, 0);
290
291 let _ = gen.generate_batch();
293
294 assert_eq!(gen.customers[0].velocity_count, 10);
296 }
297
298 #[test]
299 fn test_velocity_no_increment_normal() {
300 let config = GeneratorConfig {
301 batch_size: 10,
302 customer_count: 1,
303 suspicious_rate: 0, ..Default::default()
305 };
306 let mut gen = TransactionGenerator::new(config);
307
308 assert_eq!(gen.customers[0].velocity_count, 0);
310
311 let _ = gen.generate_batch();
313
314 assert_eq!(gen.customers[0].velocity_count, 0);
316 }
317}