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