1use datasynth_core::models::{
4 Department, OrganizationStructure, User, UserPersona, UserPool, WorkingHoursPattern,
5};
6use datasynth_core::templates::{MultiCultureNameGenerator, NameCulture, PersonName};
7use rand::prelude::*;
8use rand_chacha::ChaCha8Rng;
9
10#[derive(Debug, Clone)]
12pub struct UserGeneratorConfig {
13 pub culture_distribution: Vec<(NameCulture, f64)>,
15 pub email_domain: String,
17 pub generate_realistic_names: bool,
19}
20
21impl Default for UserGeneratorConfig {
22 fn default() -> Self {
23 Self {
24 culture_distribution: vec![
25 (NameCulture::WesternUs, 0.40),
26 (NameCulture::Hispanic, 0.20),
27 (NameCulture::German, 0.10),
28 (NameCulture::French, 0.05),
29 (NameCulture::Chinese, 0.10),
30 (NameCulture::Japanese, 0.05),
31 (NameCulture::Indian, 0.10),
32 ],
33 email_domain: "company.com".to_string(),
34 generate_realistic_names: true,
35 }
36 }
37}
38
39pub struct UserGenerator {
41 rng: ChaCha8Rng,
42 seed: u64,
43 name_generator: MultiCultureNameGenerator,
44 config: UserGeneratorConfig,
45 user_counter: usize,
46}
47
48impl UserGenerator {
49 pub fn new(seed: u64) -> Self {
51 Self::with_config(seed, UserGeneratorConfig::default())
52 }
53
54 pub fn with_config(seed: u64, config: UserGeneratorConfig) -> Self {
56 let mut name_gen =
57 MultiCultureNameGenerator::with_distribution(config.culture_distribution.clone());
58 name_gen.set_email_domain(&config.email_domain);
59
60 Self {
61 rng: ChaCha8Rng::seed_from_u64(seed),
62 seed,
63 name_generator: name_gen,
64 config,
65 user_counter: 0,
66 }
67 }
68
69 pub fn generate_user(&mut self, persona: UserPersona, department: Option<&Department>) -> User {
71 self.user_counter += 1;
72
73 let (user_id, display_name, email) = if self.config.generate_realistic_names {
74 let name = self.name_generator.generate_name(&mut self.rng);
75 let user_id = name.to_user_id(self.user_counter);
76 let email = self.name_generator.generate_email(&name);
77 (user_id, name.display_name, Some(email))
78 } else {
79 let user_id = User::generate_username(persona, self.user_counter);
80 let display_name = format!("{:?} {}", persona, self.user_counter);
81 (user_id, display_name, None)
82 };
83
84 let working_hours = if persona.is_human() {
85 self.select_working_hours_pattern()
86 } else {
87 WorkingHoursPattern::batch_processing()
88 };
89
90 let mut user = User::new(user_id, display_name, persona);
91 user.email = email;
92 user.working_hours = working_hours;
93
94 if let Some(dept) = department {
95 user.department = Some(dept.name.clone());
96 user.cost_centers = vec![dept.cost_center.clone()];
97 }
98
99 user
100 }
101
102 pub fn generate_user_with_name(
104 &mut self,
105 name: PersonName,
106 persona: UserPersona,
107 department: Option<&Department>,
108 ) -> User {
109 self.user_counter += 1;
110
111 let user_id = name.to_user_id(self.user_counter);
112 let email = self.name_generator.generate_email(&name);
113
114 let working_hours = if persona.is_human() {
115 self.select_working_hours_pattern()
116 } else {
117 WorkingHoursPattern::batch_processing()
118 };
119
120 let mut user = User::new(user_id, name.display_name, persona);
121 user.email = Some(email);
122 user.working_hours = working_hours;
123
124 if let Some(dept) = department {
125 user.department = Some(dept.name.clone());
126 user.cost_centers = vec![dept.cost_center.clone()];
127 }
128
129 user
130 }
131
132 fn select_working_hours_pattern(&mut self) -> WorkingHoursPattern {
134 let roll: f64 = self.rng.gen();
135 if roll < 0.5 {
136 WorkingHoursPattern::us_standard()
137 } else if roll < 0.75 {
138 WorkingHoursPattern::european()
139 } else {
140 WorkingHoursPattern::asian()
141 }
142 }
143
144 pub fn generate_from_organization(
146 &mut self,
147 org: &OrganizationStructure,
148 company_codes: &[String],
149 ) -> UserPool {
150 let mut pool = UserPool::new();
151
152 for dept in &org.departments {
153 self.generate_department_users(&mut pool, dept, company_codes);
154 }
155
156 pool
157 }
158
159 fn generate_department_users(
161 &mut self,
162 pool: &mut UserPool,
163 dept: &Department,
164 company_codes: &[String],
165 ) {
166 let headcount = &dept.standard_headcount;
167
168 for _ in 0..headcount.junior_accountant {
170 let mut user = self.generate_user(UserPersona::JuniorAccountant, Some(dept));
171 user.company_codes = company_codes.to_vec();
172 pool.add_user(user);
173 }
174
175 for _ in 0..headcount.senior_accountant {
177 let mut user = self.generate_user(UserPersona::SeniorAccountant, Some(dept));
178 user.company_codes = company_codes.to_vec();
179 pool.add_user(user);
180 }
181
182 for _ in 0..headcount.controller {
184 let mut user = self.generate_user(UserPersona::Controller, Some(dept));
185 user.company_codes = company_codes.to_vec();
186 pool.add_user(user);
187 }
188
189 for _ in 0..headcount.manager {
191 let mut user = self.generate_user(UserPersona::Manager, Some(dept));
192 user.company_codes = company_codes.to_vec();
193 pool.add_user(user);
194 }
195
196 for _ in 0..headcount.executive {
198 let mut user = self.generate_user(UserPersona::Executive, Some(dept));
199 user.company_codes = company_codes.to_vec();
200 pool.add_user(user);
201 }
202
203 for _ in 0..headcount.automated_system {
205 let mut user = self.generate_user(UserPersona::AutomatedSystem, Some(dept));
206 user.company_codes = company_codes.to_vec();
207 pool.add_user(user);
208 }
209 }
210
211 pub fn generate_simple_pool(
213 &mut self,
214 junior_count: usize,
215 senior_count: usize,
216 controller_count: usize,
217 manager_count: usize,
218 automated_count: usize,
219 company_codes: &[String],
220 ) -> UserPool {
221 let mut pool = UserPool::new();
222
223 for _ in 0..junior_count {
224 let mut user = self.generate_user(UserPersona::JuniorAccountant, None);
225 user.company_codes = company_codes.to_vec();
226 pool.add_user(user);
227 }
228
229 for _ in 0..senior_count {
230 let mut user = self.generate_user(UserPersona::SeniorAccountant, None);
231 user.company_codes = company_codes.to_vec();
232 pool.add_user(user);
233 }
234
235 for _ in 0..controller_count {
236 let mut user = self.generate_user(UserPersona::Controller, None);
237 user.company_codes = company_codes.to_vec();
238 pool.add_user(user);
239 }
240
241 for _ in 0..manager_count {
242 let mut user = self.generate_user(UserPersona::Manager, None);
243 user.company_codes = company_codes.to_vec();
244 pool.add_user(user);
245 }
246
247 for _ in 0..automated_count {
248 let mut user = self.generate_user(UserPersona::AutomatedSystem, None);
249 user.company_codes = company_codes.to_vec();
250 pool.add_user(user);
251 }
252
253 pool
254 }
255
256 pub fn generate_standard(&mut self, company_codes: &[String]) -> UserPool {
258 self.generate_simple_pool(10, 5, 2, 3, 20, company_codes)
259 }
260
261 pub fn reset(&mut self) {
263 self.rng = ChaCha8Rng::seed_from_u64(self.seed);
264 self.user_counter = 0;
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_user_generation() {
274 let mut gen = UserGenerator::new(42);
275
276 let user = gen.generate_user(UserPersona::SeniorAccountant, None);
277
278 assert!(!user.user_id.is_empty());
279 assert!(!user.display_name.is_empty());
280 assert!(user.email.is_some());
281 assert_eq!(user.persona, UserPersona::SeniorAccountant);
282 }
283
284 #[test]
285 fn test_generate_standard_pool() {
286 let mut gen = UserGenerator::new(42);
287 let pool = gen.generate_standard(&["1000".to_string()]);
288
289 assert_eq!(pool.users.len(), 40); assert!(!pool
293 .get_users_by_persona(UserPersona::JuniorAccountant)
294 .is_empty());
295 assert!(!pool
296 .get_users_by_persona(UserPersona::AutomatedSystem)
297 .is_empty());
298 }
299
300 #[test]
301 fn test_generate_from_organization() {
302 let mut gen = UserGenerator::new(42);
303 let org = OrganizationStructure::standard("1000");
304 let pool = gen.generate_from_organization(&org, &["1000".to_string()]);
305
306 assert!(pool.users.len() > 20);
308
309 let has_dept_users = pool.users.iter().any(|u| u.department.is_some());
311 assert!(has_dept_users);
312 }
313
314 #[test]
315 fn test_deterministic_generation() {
316 let mut gen1 = UserGenerator::new(42);
317 let mut gen2 = UserGenerator::new(42);
318
319 let user1 = gen1.generate_user(UserPersona::SeniorAccountant, None);
320 let user2 = gen2.generate_user(UserPersona::SeniorAccountant, None);
321
322 assert_eq!(user1.user_id, user2.user_id);
323 assert_eq!(user1.display_name, user2.display_name);
324 }
325
326 #[test]
327 fn test_generic_names() {
328 let config = UserGeneratorConfig {
329 generate_realistic_names: false,
330 ..Default::default()
331 };
332 let mut gen = UserGenerator::with_config(42, config);
333
334 let user = gen.generate_user(UserPersona::JuniorAccountant, None);
335
336 assert!(user.user_id.starts_with("JACC"));
338 }
339}