datasynth_generators/treasury/
bank_guarantee_generator.rs1use chrono::NaiveDate;
7use datasynth_core::utils::seeded_rng;
8use rand::prelude::*;
9use rand_chacha::ChaCha8Rng;
10use rust_decimal::Decimal;
11
12use datasynth_config::schema::BankGuaranteeSchemaConfig;
13use datasynth_core::models::{BankGuarantee, GuaranteeStatus, GuaranteeType};
14
15const ISSUING_BANKS: &[&str] = &[
20 "Deutsche Bank",
21 "HSBC",
22 "Citibank",
23 "BNP Paribas",
24 "Standard Chartered",
25 "JPMorgan Chase",
26 "Barclays",
27 "Commerzbank",
28 "Societe Generale",
29 "ING Bank",
30];
31
32const GUARANTEE_TYPES: &[GuaranteeType] = &[
33 GuaranteeType::PerformanceBond,
34 GuaranteeType::BankGuarantee,
35 GuaranteeType::StandbyLc,
36 GuaranteeType::CommercialLc,
37];
38
39pub struct BankGuaranteeGenerator {
45 rng: ChaCha8Rng,
46 config: BankGuaranteeSchemaConfig,
47 counter: u64,
48}
49
50impl BankGuaranteeGenerator {
51 pub fn new(config: BankGuaranteeSchemaConfig, seed: u64) -> Self {
53 Self {
54 rng: seeded_rng(seed, 0),
55 config,
56 counter: 0,
57 }
58 }
59
60 pub fn generate(
66 &mut self,
67 entity_id: &str,
68 currency: &str,
69 start_date: NaiveDate,
70 vendors: &[String],
71 ) -> Vec<BankGuarantee> {
72 if vendors.is_empty() {
73 return Vec::new();
74 }
75
76 let count = self.config.count as usize;
77 let mut guarantees = Vec::with_capacity(count);
78
79 for _ in 0..count {
80 self.counter += 1;
81 let id = format!("BG-{:06}", self.counter);
82
83 let gt_idx = self.rng.random_range(0..GUARANTEE_TYPES.len());
85 let guarantee_type = GUARANTEE_TYPES[gt_idx];
86
87 let amount_f = self.rng.random_range(5_000.0f64..500_000.0);
89 let amount = Decimal::try_from(amount_f)
90 .unwrap_or(Decimal::new(50_000, 0))
91 .round_dp(2);
92
93 let vendor_idx = self.rng.random_range(0..vendors.len());
95 let beneficiary = &vendors[vendor_idx];
96
97 let bank_idx = self.rng.random_range(0..ISSUING_BANKS.len());
99 let issuing_bank = ISSUING_BANKS[bank_idx];
100
101 let offset_days = self.rng.random_range(0i64..180);
103 let issue_date = start_date + chrono::Duration::days(offset_days);
104
105 let duration_days = self.rng.random_range(90i64..365);
107 let expiry_date = issue_date + chrono::Duration::days(duration_days);
108
109 let mut guarantee = BankGuarantee::new(
110 id,
111 entity_id,
112 guarantee_type,
113 amount,
114 currency,
115 beneficiary.as_str(),
116 issuing_bank,
117 issue_date,
118 expiry_date,
119 );
120
121 if self.rng.random_bool(0.20) {
123 guarantee = guarantee.with_status(GuaranteeStatus::Expired);
124 }
125
126 guarantees.push(guarantee);
127 }
128
129 guarantees
130 }
131}
132
133#[cfg(test)]
138#[allow(clippy::unwrap_used)]
139mod tests {
140 use super::*;
141
142 fn d(s: &str) -> NaiveDate {
143 NaiveDate::parse_from_str(s, "%Y-%m-%d").unwrap()
144 }
145
146 fn sample_vendors() -> Vec<String> {
147 vec![
148 "Acme Corp".to_string(),
149 "Widget Co".to_string(),
150 "BuildRight Ltd".to_string(),
151 ]
152 }
153
154 #[test]
155 fn test_basic_generation() {
156 let config = BankGuaranteeSchemaConfig {
157 enabled: true,
158 count: 5,
159 };
160 let mut gen = BankGuaranteeGenerator::new(config, 42);
161 let guarantees = gen.generate("C001", "USD", d("2025-01-01"), &sample_vendors());
162
163 assert_eq!(guarantees.len(), 5);
164 for g in &guarantees {
165 assert!(g.id.starts_with("BG-"));
166 assert_eq!(g.entity_id, "C001");
167 assert_eq!(g.currency, "USD");
168 assert!(g.amount > Decimal::ZERO);
169 assert!(g.expiry_date > g.issue_date);
170 assert!(!g.beneficiary.is_empty());
171 assert!(!g.issuing_bank.is_empty());
172 }
173 }
174
175 #[test]
176 fn test_deterministic() {
177 let config = BankGuaranteeSchemaConfig {
178 enabled: true,
179 count: 3,
180 };
181 let vendors = sample_vendors();
182
183 let mut gen1 = BankGuaranteeGenerator::new(config.clone(), 42);
184 let r1 = gen1.generate("C001", "USD", d("2025-01-01"), &vendors);
185
186 let mut gen2 = BankGuaranteeGenerator::new(config, 42);
187 let r2 = gen2.generate("C001", "USD", d("2025-01-01"), &vendors);
188
189 assert_eq!(r1.len(), r2.len());
190 for (a, b) in r1.iter().zip(r2.iter()) {
191 assert_eq!(a.id, b.id);
192 assert_eq!(a.amount, b.amount);
193 assert_eq!(a.guarantee_type, b.guarantee_type);
194 assert_eq!(a.beneficiary, b.beneficiary);
195 assert_eq!(a.status, b.status);
196 }
197 }
198
199 #[test]
200 fn test_empty_vendors() {
201 let config = BankGuaranteeSchemaConfig {
202 enabled: true,
203 count: 5,
204 };
205 let mut gen = BankGuaranteeGenerator::new(config, 42);
206 let guarantees = gen.generate("C001", "USD", d("2025-01-01"), &[]);
207
208 assert!(guarantees.is_empty());
209 }
210}