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)]
269#[allow(clippy::unwrap_used)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_user_generation() {
275 let mut gen = UserGenerator::new(42);
276
277 let user = gen.generate_user(UserPersona::SeniorAccountant, None);
278
279 assert!(!user.user_id.is_empty());
280 assert!(!user.display_name.is_empty());
281 assert!(user.email.is_some());
282 assert_eq!(user.persona, UserPersona::SeniorAccountant);
283 }
284
285 #[test]
286 fn test_generate_standard_pool() {
287 let mut gen = UserGenerator::new(42);
288 let pool = gen.generate_standard(&["1000".to_string()]);
289
290 assert_eq!(pool.users.len(), 40); assert!(!pool
294 .get_users_by_persona(UserPersona::JuniorAccountant)
295 .is_empty());
296 assert!(!pool
297 .get_users_by_persona(UserPersona::AutomatedSystem)
298 .is_empty());
299 }
300
301 #[test]
302 fn test_generate_from_organization() {
303 let mut gen = UserGenerator::new(42);
304 let org = OrganizationStructure::standard("1000");
305 let pool = gen.generate_from_organization(&org, &["1000".to_string()]);
306
307 assert!(pool.users.len() > 20);
309
310 let has_dept_users = pool.users.iter().any(|u| u.department.is_some());
312 assert!(has_dept_users);
313 }
314
315 #[test]
316 fn test_deterministic_generation() {
317 let mut gen1 = UserGenerator::new(42);
318 let mut gen2 = UserGenerator::new(42);
319
320 let user1 = gen1.generate_user(UserPersona::SeniorAccountant, None);
321 let user2 = gen2.generate_user(UserPersona::SeniorAccountant, None);
322
323 assert_eq!(user1.user_id, user2.user_id);
324 assert_eq!(user1.display_name, user2.display_name);
325 }
326
327 #[test]
328 fn test_generic_names() {
329 let config = UserGeneratorConfig {
330 generate_realistic_names: false,
331 ..Default::default()
332 };
333 let mut gen = UserGenerator::with_config(42, config);
334
335 let user = gen.generate_user(UserPersona::JuniorAccountant, None);
336
337 assert!(user.user_id.starts_with("JACC"));
339 }
340}