1use crate::domains::Domain;
8use crate::persona::PersonaProfile;
9use crate::Result;
10use rand::rngs::StdRng;
11use rand::Rng;
12use rand::SeedableRng;
13use std::collections::HashMap;
14
15#[derive(Debug)]
20pub struct BackstoryGenerator {
21 templates: HashMap<Domain, Vec<BackstoryTemplate>>,
23}
24
25#[derive(Debug, Clone)]
27pub struct BackstoryTemplate {
28 template: String,
30 required_traits: Vec<String>,
32}
33
34impl BackstoryGenerator {
35 pub fn new() -> Self {
37 let mut generator = Self {
38 templates: HashMap::new(),
39 };
40
41 generator.initialize_finance_templates();
43 generator.initialize_ecommerce_templates();
44 generator.initialize_healthcare_templates();
45 generator.initialize_iot_templates();
46
47 generator
48 }
49
50 pub fn generate_backstory(&self, persona: &PersonaProfile) -> Result<String> {
55 let templates = self.templates.get(&persona.domain).ok_or_else(|| {
56 crate::Error::generic(format!(
57 "No backstory templates available for domain: {:?}",
58 persona.domain
59 ))
60 })?;
61
62 let matching_templates: Vec<&BackstoryTemplate> = templates
64 .iter()
65 .filter(|template| {
66 template
67 .required_traits
68 .iter()
69 .all(|trait_name| persona.get_trait(trait_name).is_some())
70 })
71 .collect();
72
73 if matching_templates.is_empty() {
74 return Ok(self.generate_generic_backstory(persona));
76 }
77
78 let mut rng = StdRng::seed_from_u64(persona.seed);
80 let selected_template = &matching_templates[rng.random_range(0..matching_templates.len())];
81
82 let mut backstory = selected_template.template.clone();
84 for (trait_name, trait_value) in &persona.traits {
85 let placeholder = format!("{{{}}}", trait_name);
86 backstory = backstory.replace(&placeholder, trait_value);
87 }
88
89 backstory = self.fill_remaining_placeholders(&backstory, persona, &mut rng)?;
91
92 Ok(backstory)
93 }
94
95 fn generate_generic_backstory(&self, persona: &PersonaProfile) -> String {
97 match persona.domain {
98 Domain::Finance => {
99 format!(
100 "A {} user in the finance domain with {} traits.",
101 persona.id,
102 persona.traits.len()
103 )
104 }
105 Domain::Ecommerce => {
106 format!(
107 "An e-commerce customer with ID {} and {} preferences.",
108 persona.id,
109 persona.traits.len()
110 )
111 }
112 Domain::Healthcare => {
113 format!(
114 "A healthcare patient with ID {} and {} medical attributes.",
115 persona.id,
116 persona.traits.len()
117 )
118 }
119 Domain::Iot => {
120 format!(
121 "An IoT device or user with ID {} and {} characteristics.",
122 persona.id,
123 persona.traits.len()
124 )
125 }
126 _ => format!("A user with ID {} in the {:?} domain.", persona.id, persona.domain),
127 }
128 }
129
130 fn fill_remaining_placeholders(
132 &self,
133 template: &str,
134 _persona: &PersonaProfile,
135 rng: &mut StdRng,
136 ) -> Result<String> {
137 let mut result = template.to_string();
138
139 let common_replacements: HashMap<&str, Vec<&str>> = [
141 ("{age_group}", vec!["young", "middle-aged", "senior"]),
142 ("{location}", vec!["urban", "suburban", "rural"]),
143 ("{activity_level}", vec!["active", "moderate", "low"]),
144 ]
145 .into_iter()
146 .collect();
147
148 for (placeholder, options) in common_replacements {
149 if result.contains(placeholder) {
150 let value = options[rng.random_range(0..options.len())];
151 result = result.replace(placeholder, value);
152 }
153 }
154
155 while let Some(start) = result.find('{') {
157 if let Some(end) = result[start..].find('}') {
158 result.replace_range(start..start + end + 1, "");
159 } else {
160 break;
161 }
162 }
163
164 Ok(result)
165 }
166
167 fn initialize_finance_templates(&mut self) {
169 let templates = vec![
170 BackstoryTemplate {
171 template: "A {spending_level} spender with a {account_type} account, preferring {preferred_currency} transactions. Account age: {account_age}.".to_string(),
172 required_traits: vec!["spending_level".to_string(), "account_type".to_string()],
173 },
174 BackstoryTemplate {
175 template: "Finance professional with {account_type} account and {transaction_frequency} transaction activity. Prefers {preferred_currency}.".to_string(),
176 required_traits: vec!["account_type".to_string(), "transaction_frequency".to_string()],
177 },
178 BackstoryTemplate {
179 template: "A {spending_level} spending customer with {account_type} {account_age} account history. Primary currency: {preferred_currency}.".to_string(),
180 required_traits: vec!["spending_level".to_string(), "account_age".to_string()],
181 },
182 ];
183
184 self.templates.insert(Domain::Finance, templates);
185 }
186
187 fn initialize_ecommerce_templates(&mut self) {
189 let templates = vec![
190 BackstoryTemplate {
191 template: "A {customer_segment} customer who makes {purchase_frequency} purchases, primarily in {preferred_category}. Shipping preference: {preferred_shipping}.".to_string(),
192 required_traits: vec!["customer_segment".to_string(), "purchase_frequency".to_string()],
193 },
194 BackstoryTemplate {
195 template: "{customer_segment} shopper with {purchase_frequency} buying habits. Favorite category: {preferred_category}. Return frequency: {return_frequency}.".to_string(),
196 required_traits: vec!["customer_segment".to_string(), "preferred_category".to_string()],
197 },
198 BackstoryTemplate {
199 template: "E-commerce customer in the {preferred_category} category with {purchase_frequency} purchase patterns. Prefers {preferred_shipping} delivery.".to_string(),
200 required_traits: vec!["preferred_category".to_string(), "preferred_shipping".to_string()],
201 },
202 ];
203
204 self.templates.insert(Domain::Ecommerce, templates);
205 }
206
207 fn initialize_healthcare_templates(&mut self) {
209 let templates = vec![
210 BackstoryTemplate {
211 template: "A {age_group} patient with {insurance_type} insurance and blood type {blood_type}. Visit frequency: {visit_frequency}. Chronic conditions: {chronic_conditions}.".to_string(),
212 required_traits: vec!["insurance_type".to_string(), "blood_type".to_string()],
213 },
214 BackstoryTemplate {
215 template: "{age_group} patient covered by {insurance_type} insurance. {visit_frequency} medical visits with {chronic_conditions} chronic conditions.".to_string(),
216 required_traits: vec!["age_group".to_string(), "insurance_type".to_string(), "visit_frequency".to_string()],
217 },
218 BackstoryTemplate {
219 template: "Healthcare patient with {blood_type} blood type and {insurance_type} coverage. {visit_frequency} visits, {chronic_conditions} chronic conditions.".to_string(),
220 required_traits: vec!["blood_type".to_string(), "insurance_type".to_string()],
221 },
222 ];
223
224 self.templates.insert(Domain::Healthcare, templates);
225 }
226
227 fn initialize_iot_templates(&mut self) {
229 let templates = vec![
230 BackstoryTemplate {
231 template: "IoT device or user in a {location} environment with {activity_level} activity patterns.".to_string(),
232 required_traits: vec![],
233 },
234 BackstoryTemplate {
235 template: "Connected device user with {activity_level} usage patterns in a {location} setting.".to_string(),
236 required_traits: vec![],
237 },
238 ];
239
240 self.templates.insert(Domain::Iot, templates);
241 }
242
243 pub fn add_template(&mut self, domain: Domain, template: BackstoryTemplate) {
245 self.templates.entry(domain).or_default().push(template);
246 }
247}
248
249impl Default for BackstoryGenerator {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::persona::PersonaProfile;
259 use std::collections::HashMap;
260
261 #[test]
262 fn test_backstory_generator_new() {
263 let generator = BackstoryGenerator::new();
264 assert!(generator.templates.contains_key(&Domain::Finance));
265 assert!(generator.templates.contains_key(&Domain::Ecommerce));
266 assert!(generator.templates.contains_key(&Domain::Healthcare));
267 }
268
269 #[test]
270 fn test_generate_finance_backstory() {
271 let generator = BackstoryGenerator::new();
272 let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
273 persona.set_trait("spending_level".to_string(), "high".to_string());
274 persona.set_trait("account_type".to_string(), "premium".to_string());
275 persona.set_trait("preferred_currency".to_string(), "USD".to_string());
276 persona.set_trait("account_age".to_string(), "long_term".to_string());
277
278 let backstory = generator.generate_backstory(&persona).unwrap();
279 assert!(!backstory.is_empty());
280 assert!(backstory.contains("high"));
281 assert!(backstory.contains("premium"));
282 }
283
284 #[test]
285 fn test_generate_ecommerce_backstory() {
286 let generator = BackstoryGenerator::new();
287 let mut persona = PersonaProfile::new("customer456".to_string(), Domain::Ecommerce);
288 persona.set_trait("customer_segment".to_string(), "VIP".to_string());
289 persona.set_trait("purchase_frequency".to_string(), "frequent".to_string());
290 persona.set_trait("preferred_category".to_string(), "electronics".to_string());
291 persona.set_trait("preferred_shipping".to_string(), "express".to_string());
292
293 let backstory = generator.generate_backstory(&persona).unwrap();
294 assert!(!backstory.is_empty());
295 assert!(backstory.contains("VIP") || backstory.contains("electronics"));
296 }
297
298 #[test]
299 fn test_generate_healthcare_backstory() {
300 let generator = BackstoryGenerator::new();
301 let mut persona = PersonaProfile::new("patient789".to_string(), Domain::Healthcare);
302 persona.set_trait("insurance_type".to_string(), "private".to_string());
303 persona.set_trait("blood_type".to_string(), "O+".to_string());
304 persona.set_trait("age_group".to_string(), "adult".to_string());
305 persona.set_trait("visit_frequency".to_string(), "regular".to_string());
306 persona.set_trait("chronic_conditions".to_string(), "single".to_string());
307
308 let backstory = generator.generate_backstory(&persona).unwrap();
309 assert!(!backstory.is_empty());
310 assert!(backstory.contains("private") || backstory.contains("O+"));
311 }
312
313 #[test]
314 fn test_generate_generic_backstory() {
315 let generator = BackstoryGenerator::new();
316 let persona = PersonaProfile::new("user999".to_string(), Domain::General);
317
318 let backstory = generator.generate_backstory(&persona);
320 if let Ok(backstory) = backstory {
322 assert!(!backstory.is_empty());
323 }
324 }
325
326 #[test]
327 fn test_deterministic_backstory() {
328 let generator = BackstoryGenerator::new();
329 let mut persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
330 persona1.set_trait("spending_level".to_string(), "high".to_string());
331 persona1.set_trait("account_type".to_string(), "premium".to_string());
332
333 let mut persona2 = PersonaProfile::new("user123".to_string(), Domain::Finance);
334 persona2.set_trait("spending_level".to_string(), "high".to_string());
335 persona2.set_trait("account_type".to_string(), "premium".to_string());
336
337 assert_eq!(persona1.seed, persona2.seed);
339 let backstory1 = generator.generate_backstory(&persona1).unwrap();
340 let backstory2 = generator.generate_backstory(&persona2).unwrap();
341 assert_eq!(backstory1, backstory2);
342 }
343}