1use datasynth_core::models::banking::MerchantCategoryCode;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Merchant {
11 pub merchant_id: Uuid,
13 pub name: String,
15 pub mcc: MerchantCategoryCode,
17 pub country: String,
19 pub city: Option<String>,
21 pub is_online: bool,
23 pub typical_amount_range: (Decimal, Decimal),
25 pub is_high_risk: bool,
27}
28
29impl Merchant {
30 pub fn new(merchant_id: Uuid, name: &str, mcc: MerchantCategoryCode, country: &str) -> Self {
32 Self {
33 merchant_id,
34 name: name.to_string(),
35 mcc,
36 country: country.to_string(),
37 city: None,
38 is_online: false,
39 typical_amount_range: (Decimal::from(10), Decimal::from(500)),
40 is_high_risk: mcc.is_high_risk(),
41 }
42 }
43
44 pub fn online(merchant_id: Uuid, name: &str, mcc: MerchantCategoryCode) -> Self {
46 Self {
47 merchant_id,
48 name: name.to_string(),
49 mcc,
50 country: "US".to_string(),
51 city: None,
52 is_online: true,
53 typical_amount_range: (Decimal::from(10), Decimal::from(1000)),
54 is_high_risk: mcc.is_high_risk(),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Employer {
62 pub employer_id: Uuid,
64 pub name: String,
66 pub country: String,
68 pub industry_code: Option<String>,
70 pub salary_range: (Decimal, Decimal),
72 pub pay_frequency: PayFrequency,
74}
75
76impl Employer {
77 pub fn new(employer_id: Uuid, name: &str, country: &str) -> Self {
79 Self {
80 employer_id,
81 name: name.to_string(),
82 country: country.to_string(),
83 industry_code: None,
84 salary_range: (Decimal::from(3000), Decimal::from(10000)),
85 pay_frequency: PayFrequency::Monthly,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
92#[serde(rename_all = "snake_case")]
93pub enum PayFrequency {
94 Weekly,
96 BiWeekly,
98 SemiMonthly,
100 #[default]
102 Monthly,
103}
104
105impl PayFrequency {
106 pub fn interval_days(&self) -> u32 {
108 match self {
109 Self::Weekly => 7,
110 Self::BiWeekly => 14,
111 Self::SemiMonthly => 15,
112 Self::Monthly => 30,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct UtilityCompany {
120 pub utility_id: Uuid,
122 pub name: String,
124 pub utility_type: UtilityType,
126 pub country: String,
128 pub typical_bill_range: (Decimal, Decimal),
130}
131
132impl UtilityCompany {
133 pub fn new(utility_id: Uuid, name: &str, utility_type: UtilityType, country: &str) -> Self {
135 let bill_range = match utility_type {
136 UtilityType::Electric => (Decimal::from(50), Decimal::from(300)),
137 UtilityType::Gas => (Decimal::from(30), Decimal::from(200)),
138 UtilityType::Water => (Decimal::from(20), Decimal::from(100)),
139 UtilityType::Internet => (Decimal::from(40), Decimal::from(150)),
140 UtilityType::Phone => (Decimal::from(30), Decimal::from(200)),
141 UtilityType::Cable => (Decimal::from(50), Decimal::from(200)),
142 UtilityType::Streaming => (Decimal::from(10), Decimal::from(50)),
143 UtilityType::Insurance => (Decimal::from(100), Decimal::from(500)),
144 };
145
146 Self {
147 utility_id,
148 name: name.to_string(),
149 utility_type,
150 country: country.to_string(),
151 typical_bill_range: bill_range,
152 }
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
158#[serde(rename_all = "snake_case")]
159pub enum UtilityType {
160 Electric,
162 Gas,
164 Water,
166 Internet,
168 Phone,
170 Cable,
172 Streaming,
174 Insurance,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct GovernmentAgency {
181 pub agency_id: Uuid,
183 pub name: String,
185 pub agency_type: GovernmentAgencyType,
187 pub country: String,
189}
190
191impl GovernmentAgency {
192 pub fn new(
194 agency_id: Uuid,
195 name: &str,
196 agency_type: GovernmentAgencyType,
197 country: &str,
198 ) -> Self {
199 Self {
200 agency_id,
201 name: name.to_string(),
202 agency_type,
203 country: country.to_string(),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
210#[serde(rename_all = "snake_case")]
211pub enum GovernmentAgencyType {
212 TaxAuthority,
214 SocialSecurity,
216 Unemployment,
218 Veterans,
220 Local,
222 Federal,
224 State,
226}
227
228#[derive(Debug, Clone, Default)]
230pub struct CounterpartyPool {
231 pub merchants: Vec<Merchant>,
233 pub employers: Vec<Employer>,
235 pub utilities: Vec<UtilityCompany>,
237 pub government_agencies: Vec<GovernmentAgency>,
239}
240
241impl CounterpartyPool {
242 pub fn new() -> Self {
244 Self::default()
245 }
246
247 pub fn add_merchant(&mut self, merchant: Merchant) {
249 self.merchants.push(merchant);
250 }
251
252 pub fn add_employer(&mut self, employer: Employer) {
254 self.employers.push(employer);
255 }
256
257 pub fn add_utility(&mut self, utility: UtilityCompany) {
259 self.utilities.push(utility);
260 }
261
262 pub fn add_government(&mut self, agency: GovernmentAgency) {
264 self.government_agencies.push(agency);
265 }
266
267 pub fn standard() -> Self {
269 let mut pool = Self::new();
270
271 let merchants = [
273 ("Walmart", MerchantCategoryCode::GROCERY_STORES),
274 ("Amazon", MerchantCategoryCode(5999)),
275 ("Target", MerchantCategoryCode::DEPARTMENT_STORES),
276 ("Starbucks", MerchantCategoryCode::RESTAURANTS),
277 ("Shell Gas", MerchantCategoryCode::GAS_STATIONS),
278 ("CVS Pharmacy", MerchantCategoryCode::DRUG_STORES),
279 ("Netflix", MerchantCategoryCode(4899)),
280 ("Uber", MerchantCategoryCode(4121)),
281 ];
282
283 for (name, mcc) in merchants {
284 pool.add_merchant(Merchant::new(Uuid::new_v4(), name, mcc, "US"));
285 }
286
287 let utilities = [
289 ("Electric Company", UtilityType::Electric),
290 ("Gas Company", UtilityType::Gas),
291 ("Water Utility", UtilityType::Water),
292 ("Comcast", UtilityType::Internet),
293 ("AT&T", UtilityType::Phone),
294 ("State Farm Insurance", UtilityType::Insurance),
295 ];
296
297 for (name, utype) in utilities {
298 pool.add_utility(UtilityCompany::new(Uuid::new_v4(), name, utype, "US"));
299 }
300
301 pool.add_government(GovernmentAgency::new(
303 Uuid::new_v4(),
304 "IRS",
305 GovernmentAgencyType::TaxAuthority,
306 "US",
307 ));
308 pool.add_government(GovernmentAgency::new(
309 Uuid::new_v4(),
310 "Social Security Administration",
311 GovernmentAgencyType::SocialSecurity,
312 "US",
313 ));
314
315 pool
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_merchant_creation() {
325 let merchant = Merchant::new(
326 Uuid::new_v4(),
327 "Test Store",
328 MerchantCategoryCode::GROCERY_STORES,
329 "US",
330 );
331 assert!(!merchant.is_high_risk);
332 }
333
334 #[test]
335 fn test_counterparty_pool() {
336 let pool = CounterpartyPool::standard();
337 assert!(!pool.merchants.is_empty());
338 assert!(!pool.utilities.is_empty());
339 }
340
341 #[test]
342 fn test_pay_frequency() {
343 assert_eq!(PayFrequency::Weekly.interval_days(), 7);
344 assert_eq!(PayFrequency::Monthly.interval_days(), 30);
345 }
346}