1use crate::domains::{Domain, DomainGenerator};
9use crate::Result;
10use rand::rngs::StdRng;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::hash::{Hash, Hasher};
15use std::sync::{Arc, RwLock};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PersonaProfile {
20 pub id: String,
22 pub domain: Domain,
24 pub traits: HashMap<String, String>,
26 pub seed: u64,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub backstory: Option<String>,
31 #[serde(default)]
35 pub relationships: HashMap<String, Vec<String>>,
36 #[serde(default)]
38 pub metadata: HashMap<String, Value>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub lifecycle: Option<crate::persona_lifecycle::PersonaLifecycle>,
42}
43
44impl PersonaProfile {
45 pub fn new(id: String, domain: Domain) -> Self {
50 let seed = Self::derive_seed(&id, domain);
51 Self {
52 id,
53 domain,
54 traits: HashMap::new(),
55 seed,
56 backstory: None,
57 relationships: HashMap::new(),
58 metadata: HashMap::new(),
59 lifecycle: None,
60 }
61 }
62
63 pub fn with_traits(id: String, domain: Domain, traits: HashMap<String, String>) -> Self {
65 let mut persona = Self::new(id, domain);
66 persona.traits = traits;
67 persona
68 }
69
70 pub fn set_lifecycle(&mut self, lifecycle: crate::persona_lifecycle::PersonaLifecycle) {
72 self.lifecycle = Some(lifecycle);
73 }
74
75 pub fn get_lifecycle(&self) -> Option<&crate::persona_lifecycle::PersonaLifecycle> {
77 self.lifecycle.as_ref()
78 }
79
80 pub fn get_lifecycle_mut(&mut self) -> Option<&mut crate::persona_lifecycle::PersonaLifecycle> {
82 self.lifecycle.as_mut()
83 }
84
85 pub fn update_lifecycle_state(&mut self, current_time: chrono::DateTime<chrono::Utc>) {
89 if let Some(ref mut lifecycle) = self.lifecycle {
90 if let Some((new_state, _rule)) = lifecycle.transition_if_elapsed(current_time) {
91 lifecycle.transition_to(new_state, current_time);
92
93 let effects = lifecycle.apply_lifecycle_effects();
95 for (key, value) in effects {
96 self.set_trait(key, value);
97 }
98 }
99 }
100 }
101
102 fn derive_seed(id: &str, domain: Domain) -> u64 {
107 use std::collections::hash_map::DefaultHasher;
108 let mut hasher = DefaultHasher::new();
109 id.hash(&mut hasher);
110 domain.as_str().hash(&mut hasher);
111 hasher.finish()
112 }
113
114 pub fn set_trait(&mut self, name: String, value: String) {
116 self.traits.insert(name, value);
117 }
118
119 pub fn get_trait(&self, name: &str) -> Option<&String> {
121 self.traits.get(name)
122 }
123
124 pub fn set_metadata(&mut self, key: String, value: Value) {
126 self.metadata.insert(key, value);
127 }
128
129 pub fn get_metadata(&self, key: &str) -> Option<&Value> {
131 self.metadata.get(key)
132 }
133
134 pub fn set_backstory(&mut self, backstory: String) {
139 self.backstory = Some(backstory);
140 }
141
142 pub fn get_backstory(&self) -> Option<&String> {
144 self.backstory.as_ref()
145 }
146
147 pub fn has_backstory(&self) -> bool {
149 self.backstory.is_some()
150 }
151
152 pub fn add_relationship(&mut self, relationship_type: String, related_persona_id: String) {
158 self.relationships
159 .entry(relationship_type)
160 .or_default()
161 .push(related_persona_id);
162 }
163
164 pub fn get_relationships(&self, relationship_type: &str) -> Option<&Vec<String>> {
168 self.relationships.get(relationship_type)
169 }
170
171 pub fn get_related_personas(&self, relationship_type: &str) -> Vec<String> {
175 self.relationships.get(relationship_type).cloned().unwrap_or_default()
176 }
177
178 pub fn get_relationship_types(&self) -> Vec<String> {
180 self.relationships.keys().cloned().collect()
181 }
182
183 pub fn remove_relationship(
188 &mut self,
189 relationship_type: &str,
190 related_persona_id: &str,
191 ) -> bool {
192 if let Some(related_ids) = self.relationships.get_mut(relationship_type) {
193 if let Some(pos) = related_ids.iter().position(|id| id == related_persona_id) {
194 related_ids.remove(pos);
195 if related_ids.is_empty() {
197 self.relationships.remove(relationship_type);
198 }
199 return true;
200 }
201 }
202 false
203 }
204}
205
206#[derive(Debug, Clone)]
211pub struct PersonaRegistry {
212 personas: Arc<RwLock<HashMap<String, PersonaProfile>>>,
214 default_traits: HashMap<String, String>,
216 graph: Arc<crate::persona_graph::PersonaGraph>,
218}
219
220impl PersonaRegistry {
221 pub fn new() -> Self {
223 Self {
224 personas: Arc::new(RwLock::new(HashMap::new())),
225 default_traits: HashMap::new(),
226 graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
227 }
228 }
229
230 pub fn with_default_traits(default_traits: HashMap<String, String>) -> Self {
232 Self {
233 personas: Arc::new(RwLock::new(HashMap::new())),
234 default_traits,
235 graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
236 }
237 }
238
239 pub fn graph(&self) -> Arc<crate::persona_graph::PersonaGraph> {
241 Arc::clone(&self.graph)
242 }
243
244 pub fn get_or_create_persona(&self, id: String, domain: Domain) -> PersonaProfile {
249 let personas = self.personas.read().expect("persona registry read lock poisoned");
250
251 if let Some(persona) = personas.get(&id) {
253 return persona.clone();
254 }
255 drop(personas);
256
257 let mut persona = PersonaProfile::new(id.clone(), domain);
259 for (key, value) in &self.default_traits {
260 persona.set_trait(key.clone(), value.clone());
261 }
262
263 let mut personas = self.personas.write().expect("persona registry write lock poisoned");
265 personas.insert(id.clone(), persona.clone());
266
267 let entity_type = persona.domain.as_str().to_string();
269 let graph_node = crate::persona_graph::PersonaNode::new(id.clone(), entity_type);
270 self.graph.add_node(graph_node);
271
272 persona
273 }
274
275 pub fn get_persona(&self, id: &str) -> Option<PersonaProfile> {
277 let personas = self.personas.read().expect("persona registry read lock poisoned");
278 personas.get(id).cloned()
279 }
280
281 pub fn update_persona(&self, id: &str, traits: HashMap<String, String>) -> Result<()> {
283 let mut personas = self.personas.write().map_err(|e| {
284 crate::Error::LockPoisoned(format!("persona registry write lock: {}", e))
285 })?;
286 if let Some(persona) = personas.get_mut(id) {
287 for (key, value) in traits {
288 persona.set_trait(key, value);
289 }
290 Ok(())
291 } else {
292 Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
293 }
294 }
295
296 pub fn update_persona_backstory(&self, id: &str, backstory: String) -> Result<()> {
300 let mut personas = self.personas.write().map_err(|e| {
301 crate::Error::LockPoisoned(format!("persona registry write lock: {}", e))
302 })?;
303 if let Some(persona) = personas.get_mut(id) {
304 persona.set_backstory(backstory);
305 Ok(())
306 } else {
307 Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
308 }
309 }
310
311 pub fn update_persona_full(
316 &self,
317 id: &str,
318 traits: Option<HashMap<String, String>>,
319 backstory: Option<String>,
320 relationships: Option<HashMap<String, Vec<String>>>,
321 ) -> Result<()> {
322 let mut personas = self.personas.write().map_err(|e| {
323 crate::Error::LockPoisoned(format!("persona registry write lock: {}", e))
324 })?;
325 if let Some(persona) = personas.get_mut(id) {
326 if let Some(traits) = traits {
327 for (key, value) in traits {
328 persona.set_trait(key, value);
329 }
330 }
331 if let Some(backstory) = backstory {
332 persona.set_backstory(backstory);
333 }
334 if let Some(relationships) = relationships {
335 for (rel_type, related_ids) in relationships {
336 for related_id in related_ids {
337 persona.add_relationship(rel_type.clone(), related_id);
338 }
339 }
340 }
341 Ok(())
342 } else {
343 Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
344 }
345 }
346
347 pub fn remove_persona(&self, id: &str) -> bool {
349 let mut personas = self.personas.write().expect("persona registry write lock poisoned");
350 personas.remove(id).is_some()
351 }
352
353 pub fn list_persona_ids(&self) -> Vec<String> {
355 let personas = self.personas.read().expect("persona registry read lock poisoned");
356 personas.keys().cloned().collect()
357 }
358
359 pub fn clear(&self) {
361 let mut personas = self.personas.write().expect("persona registry write lock poisoned");
362 personas.clear();
363 }
364
365 pub fn count(&self) -> usize {
367 let personas = self.personas.read().expect("persona registry read lock poisoned");
368 personas.len()
369 }
370
371 pub fn get_related_personas(
375 &self,
376 persona_id: &str,
377 relationship_type: &str,
378 ) -> Result<Vec<PersonaProfile>> {
379 let personas = self.personas.read().map_err(|e| {
380 crate::Error::LockPoisoned(format!("persona registry read lock: {}", e))
381 })?;
382 if let Some(persona) = personas.get(persona_id) {
383 let related_ids = persona.get_related_personas(relationship_type);
384 let mut related_personas = Vec::new();
385 for related_id in related_ids {
386 if let Some(related_persona) = personas.get(&related_id) {
387 related_personas.push(related_persona.clone());
388 }
389 }
390 Ok(related_personas)
391 } else {
392 Err(crate::Error::generic(format!("Persona with ID '{}' not found", persona_id)))
393 }
394 }
395
396 pub fn find_personas_with_relationship_to(
400 &self,
401 target_persona_id: &str,
402 relationship_type: &str,
403 ) -> Vec<PersonaProfile> {
404 let personas = self.personas.read().expect("persona registry read lock poisoned");
405 let mut result = Vec::new();
406
407 for persona in personas.values() {
408 if let Some(related_ids) = persona.get_relationships(relationship_type) {
409 if related_ids.contains(&target_persona_id.to_string()) {
410 result.push(persona.clone());
411 }
412 }
413 }
414
415 result
416 }
417
418 pub fn add_relationship(
422 &self,
423 from_persona_id: &str,
424 relationship_type: String,
425 to_persona_id: String,
426 ) -> Result<()> {
427 let mut personas = self.personas.write().map_err(|e| {
428 crate::Error::LockPoisoned(format!("persona registry write lock: {}", e))
429 })?;
430 if let Some(persona) = personas.get_mut(from_persona_id) {
431 persona.add_relationship(relationship_type.clone(), to_persona_id.clone());
432
433 self.graph
435 .add_edge(from_persona_id.to_string(), to_persona_id, relationship_type);
436
437 Ok(())
438 } else {
439 Err(crate::Error::generic(format!(
440 "Persona with ID '{}' not found",
441 from_persona_id
442 )))
443 }
444 }
445
446 pub fn coherent_persona_switch<F>(
460 &self,
461 root_persona_id: &str,
462 relationship_types: Option<&[String]>,
463 update_callback: Option<F>,
464 ) -> Result<Vec<String>>
465 where
466 F: Fn(&str, &mut PersonaProfile),
467 {
468 let related_ids = self.graph.find_related_bfs(root_persona_id, relationship_types, None);
470
471 let mut updated_ids = vec![root_persona_id.to_string()];
473 updated_ids.extend(related_ids);
474
475 let mut personas = self.personas.write().map_err(|e| {
477 crate::Error::LockPoisoned(format!("persona registry write lock: {}", e))
478 })?;
479 for persona_id in &updated_ids {
480 if let Some(persona) = personas.get_mut(persona_id) {
481 if let Some(ref callback) = update_callback {
483 callback(persona_id, persona);
484 }
485 }
486 }
487
488 Ok(updated_ids)
489 }
490}
491
492impl Default for PersonaRegistry {
493 fn default() -> Self {
494 Self::new()
495 }
496}
497
498#[derive(Debug)]
503pub struct PersonaGenerator {
504 domain_generator: DomainGenerator,
506}
507
508impl PersonaGenerator {
509 pub fn new(domain: Domain) -> Self {
511 Self {
512 domain_generator: DomainGenerator::new(domain),
513 }
514 }
515
516 pub fn generate_for_persona(
521 &self,
522 persona: &PersonaProfile,
523 field_type: &str,
524 ) -> Result<Value> {
525 self.generate_for_persona_with_reality(persona, field_type, 0.0, None, None)
527 }
528
529 pub fn generate_for_persona_with_reality(
543 &self,
544 persona: &PersonaProfile,
545 field_type: &str,
546 reality_ratio: f64,
547 recorded_data: Option<&Value>,
548 real_data: Option<&Value>,
549 ) -> Result<Value> {
550 use rand::rngs::StdRng;
552 use rand::SeedableRng;
553 let mut rng = StdRng::seed_from_u64(persona.seed);
554
555 let mut synthetic_value = self.domain_generator.generate(field_type)?;
557
558 synthetic_value =
560 self.apply_persona_traits(persona, field_type, synthetic_value, &mut rng)?;
561
562 let reality_ratio = reality_ratio.clamp(0.0, 1.0);
564
565 if reality_ratio < 0.3 {
566 Ok(synthetic_value)
568 } else if reality_ratio < 0.7 {
569 if let Some(recorded) = recorded_data {
571 self.blend_values(&synthetic_value, recorded, reality_ratio)
572 } else {
573 Ok(synthetic_value)
575 }
576 } else {
577 if let Some(real) = real_data {
579 self.blend_values(&synthetic_value, real, reality_ratio)
580 } else if let Some(recorded) = recorded_data {
581 self.blend_values(&synthetic_value, recorded, reality_ratio)
583 } else {
584 Ok(synthetic_value)
586 }
587 }
588 }
589
590 fn blend_values(&self, synthetic: &Value, other: &Value, ratio: f64) -> Result<Value> {
594 match (synthetic, other) {
595 (Value::Number(syn_num), Value::Number(other_num)) => {
597 if let (Some(syn_f64), Some(other_f64)) = (syn_num.as_f64(), other_num.as_f64()) {
598 let adjusted_ratio = if ratio < 0.7 {
601 (ratio - 0.3) / 0.4
603 } else {
604 (ratio - 0.7) / 0.3
606 };
607 let blended = syn_f64 * (1.0 - adjusted_ratio) + other_f64 * adjusted_ratio;
608 Ok(Value::Number(
609 serde_json::Number::from_f64(blended).unwrap_or(syn_num.clone()),
610 ))
611 } else {
612 Ok(synthetic.clone())
613 }
614 }
615 (Value::String(_), Value::String(other_str)) => {
617 let adjusted_ratio = if ratio < 0.7 {
618 (ratio - 0.3) / 0.4
619 } else {
620 (ratio - 0.7) / 0.3
621 };
622 if adjusted_ratio >= 0.5 {
623 Ok(Value::String(other_str.clone()))
624 } else {
625 Ok(synthetic.clone())
626 }
627 }
628 (Value::Bool(_), Value::Bool(other_bool)) => {
630 let adjusted_ratio = if ratio < 0.7 {
631 (ratio - 0.3) / 0.4
632 } else {
633 (ratio - 0.7) / 0.3
634 };
635 if adjusted_ratio >= 0.5 {
636 Ok(Value::Bool(*other_bool))
637 } else {
638 Ok(synthetic.clone())
639 }
640 }
641 _ => {
643 let adjusted_ratio = if ratio < 0.7 {
644 (ratio - 0.3) / 0.4
645 } else {
646 (ratio - 0.7) / 0.3
647 };
648 if adjusted_ratio >= 0.5 {
649 Ok(other.clone())
650 } else {
651 Ok(synthetic.clone())
652 }
653 }
654 }
655 }
656
657 pub fn generate_traits_from_backstory(
662 &self,
663 persona: &PersonaProfile,
664 ) -> Result<HashMap<String, String>> {
665 let mut inferred_traits = HashMap::new();
666
667 let backstory = match persona.get_backstory() {
669 Some(bs) => bs,
670 None => return Ok(inferred_traits),
671 };
672
673 let backstory_lower = backstory.to_lowercase();
674
675 match persona.domain {
677 Domain::Finance => {
678 if backstory_lower.contains("high-spending")
680 || backstory_lower.contains("high spending")
681 || backstory_lower.contains("big spender")
682 {
683 inferred_traits.insert("spending_level".to_string(), "high".to_string());
684 } else if backstory_lower.contains("conservative")
685 || backstory_lower.contains("low spending")
686 || backstory_lower.contains("frugal")
687 {
688 inferred_traits
689 .insert("spending_level".to_string(), "conservative".to_string());
690 } else if backstory_lower.contains("moderate") {
691 inferred_traits.insert("spending_level".to_string(), "moderate".to_string());
692 }
693
694 if backstory_lower.contains("premium") {
696 inferred_traits.insert("account_type".to_string(), "premium".to_string());
697 } else if backstory_lower.contains("business") {
698 inferred_traits.insert("account_type".to_string(), "business".to_string());
699 } else if backstory_lower.contains("savings") {
700 inferred_traits.insert("account_type".to_string(), "savings".to_string());
701 } else if backstory_lower.contains("checking") {
702 inferred_traits.insert("account_type".to_string(), "checking".to_string());
703 }
704
705 let currencies = ["usd", "eur", "gbp", "jpy", "cny"];
707 for currency in ¤cies {
708 if backstory_lower.contains(currency) {
709 inferred_traits
710 .insert("preferred_currency".to_string(), currency.to_uppercase());
711 break;
712 }
713 }
714
715 if backstory_lower.contains("long-term") || backstory_lower.contains("long term") {
717 inferred_traits.insert("account_age".to_string(), "long_term".to_string());
718 } else if backstory_lower.contains("established") {
719 inferred_traits.insert("account_age".to_string(), "established".to_string());
720 } else if backstory_lower.contains("new") {
721 inferred_traits.insert("account_age".to_string(), "new".to_string());
722 }
723 }
724 Domain::Ecommerce => {
725 if backstory_lower.contains("vip") {
727 inferred_traits.insert("customer_segment".to_string(), "VIP".to_string());
728 } else if backstory_lower.contains("new") {
729 inferred_traits.insert("customer_segment".to_string(), "new".to_string());
730 } else {
731 inferred_traits.insert("customer_segment".to_string(), "regular".to_string());
732 }
733
734 if backstory_lower.contains("frequent") {
736 inferred_traits
737 .insert("purchase_frequency".to_string(), "frequent".to_string());
738 } else if backstory_lower.contains("occasional") {
739 inferred_traits
740 .insert("purchase_frequency".to_string(), "occasional".to_string());
741 } else if backstory_lower.contains("regular") {
742 inferred_traits.insert("purchase_frequency".to_string(), "regular".to_string());
743 }
744
745 let categories = ["electronics", "clothing", "books", "home", "sports"];
747 for category in &categories {
748 if backstory_lower.contains(category) {
749 inferred_traits
750 .insert("preferred_category".to_string(), category.to_string());
751 break;
752 }
753 }
754
755 if backstory_lower.contains("express") || backstory_lower.contains("overnight") {
757 inferred_traits.insert("preferred_shipping".to_string(), "express".to_string());
758 } else if backstory_lower.contains("standard") {
759 inferred_traits
760 .insert("preferred_shipping".to_string(), "standard".to_string());
761 }
762 }
763 Domain::Healthcare => {
764 if backstory_lower.contains("private") {
766 inferred_traits.insert("insurance_type".to_string(), "private".to_string());
767 } else if backstory_lower.contains("medicare") {
768 inferred_traits.insert("insurance_type".to_string(), "medicare".to_string());
769 } else if backstory_lower.contains("medicaid") {
770 inferred_traits.insert("insurance_type".to_string(), "medicaid".to_string());
771 } else if backstory_lower.contains("uninsured") {
772 inferred_traits.insert("insurance_type".to_string(), "uninsured".to_string());
773 }
774
775 let blood_types = ["a+", "a-", "b+", "b-", "ab+", "ab-", "o+", "o-"];
777 for blood_type in &blood_types {
778 if backstory_lower.contains(blood_type) {
779 inferred_traits.insert("blood_type".to_string(), blood_type.to_uppercase());
780 break;
781 }
782 }
783
784 if backstory_lower.contains("pediatric") || backstory_lower.contains("child") {
786 inferred_traits.insert("age_group".to_string(), "pediatric".to_string());
787 } else if backstory_lower.contains("senior") || backstory_lower.contains("elderly")
788 {
789 inferred_traits.insert("age_group".to_string(), "senior".to_string());
790 } else {
791 inferred_traits.insert("age_group".to_string(), "adult".to_string());
792 }
793
794 if backstory_lower.contains("frequent") {
796 inferred_traits.insert("visit_frequency".to_string(), "frequent".to_string());
797 } else if backstory_lower.contains("regular") {
798 inferred_traits.insert("visit_frequency".to_string(), "regular".to_string());
799 } else if backstory_lower.contains("occasional") {
800 inferred_traits.insert("visit_frequency".to_string(), "occasional".to_string());
801 } else if backstory_lower.contains("rare") {
802 inferred_traits.insert("visit_frequency".to_string(), "rare".to_string());
803 }
804
805 if backstory_lower.contains("multiple") || backstory_lower.contains("several") {
807 inferred_traits
808 .insert("chronic_conditions".to_string(), "multiple".to_string());
809 } else if backstory_lower.contains("single") || backstory_lower.contains("one") {
810 inferred_traits.insert("chronic_conditions".to_string(), "single".to_string());
811 } else if backstory_lower.contains("none")
812 || backstory_lower.contains("no conditions")
813 {
814 inferred_traits.insert("chronic_conditions".to_string(), "none".to_string());
815 }
816 }
817 _ => {
818 }
820 }
821
822 Ok(inferred_traits)
823 }
824
825 fn apply_persona_traits(
832 &self,
833 persona: &PersonaProfile,
834 field_type: &str,
835 value: Value,
836 _rng: &mut StdRng,
837 ) -> Result<Value> {
838 let mut effective_persona = persona.clone();
840 if persona.has_backstory() && persona.traits.is_empty() {
841 if let Ok(inferred_traits) = self.generate_traits_from_backstory(persona) {
842 for (key, val) in inferred_traits {
843 effective_persona.set_trait(key, val);
844 }
845 }
846 }
847
848 match effective_persona.domain {
849 Domain::Finance => self.apply_finance_traits(&effective_persona, field_type, value),
850 Domain::Ecommerce => self.apply_ecommerce_traits(&effective_persona, field_type, value),
851 Domain::Healthcare => {
852 self.apply_healthcare_traits(&effective_persona, field_type, value)
853 }
854 _ => Ok(value), }
856 }
857
858 fn apply_finance_traits(
860 &self,
861 persona: &PersonaProfile,
862 field_type: &str,
863 value: Value,
864 ) -> Result<Value> {
865 match field_type {
866 "amount" | "balance" | "transaction_amount" => {
867 if let Some(spending_level) = persona.get_trait("spending_level") {
869 let multiplier = match spending_level.as_str() {
870 "high" => 2.0,
871 "moderate" => 1.0,
872 "conservative" | "low" => 0.5,
873 _ => 1.0,
874 };
875
876 if let Some(num) = value.as_f64() {
877 return Ok(Value::Number(
878 serde_json::Number::from_f64(num * multiplier)
879 .unwrap_or_else(|| serde_json::Number::from(0)),
880 ));
881 }
882 }
883 Ok(value)
884 }
885 "currency" => {
886 if let Some(currency) = persona.get_trait("preferred_currency") {
888 return Ok(Value::String(currency.clone()));
889 }
890 Ok(value)
891 }
892 "account_type" => {
893 if let Some(account_type) = persona.get_trait("account_type") {
895 return Ok(Value::String(account_type.clone()));
896 }
897 Ok(value)
898 }
899 _ => Ok(value),
900 }
901 }
902
903 fn apply_ecommerce_traits(
905 &self,
906 persona: &PersonaProfile,
907 field_type: &str,
908 value: Value,
909 ) -> Result<Value> {
910 match field_type {
911 "price" | "order_total" => {
912 if let Some(segment) = persona.get_trait("customer_segment") {
914 let multiplier = match segment.as_str() {
915 "VIP" => 1.5,
916 "regular" => 1.0,
917 "new" => 0.7,
918 _ => 1.0,
919 };
920
921 if let Some(num) = value.as_f64() {
922 return Ok(Value::Number(
923 serde_json::Number::from_f64(num * multiplier)
924 .unwrap_or_else(|| serde_json::Number::from(0)),
925 ));
926 }
927 }
928 Ok(value)
929 }
930 "shipping_method" => {
931 if let Some(shipping) = persona.get_trait("preferred_shipping") {
933 return Ok(Value::String(shipping.clone()));
934 }
935 Ok(value)
936 }
937 _ => Ok(value),
938 }
939 }
940
941 fn apply_healthcare_traits(
943 &self,
944 persona: &PersonaProfile,
945 field_type: &str,
946 value: Value,
947 ) -> Result<Value> {
948 match field_type {
949 "insurance_type" => {
950 if let Some(insurance) = persona.get_trait("insurance_type") {
952 return Ok(Value::String(insurance.clone()));
953 }
954 Ok(value)
955 }
956 "blood_type" => {
957 if let Some(blood_type) = persona.get_trait("blood_type") {
959 return Ok(Value::String(blood_type.clone()));
960 }
961 Ok(value)
962 }
963 _ => Ok(value),
964 }
965 }
966}
967
968#[cfg(test)]
969mod tests {
970 use super::*;
971
972 #[test]
973 fn test_persona_profile_new() {
974 let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
975 assert_eq!(persona.id, "user123");
976 assert_eq!(persona.domain, Domain::Finance);
977 assert!(persona.traits.is_empty());
978 assert!(persona.seed > 0);
979 }
980
981 #[test]
982 fn test_persona_profile_deterministic_seed() {
983 let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
984 let persona2 = PersonaProfile::new("user123".to_string(), Domain::Finance);
985
986 assert_eq!(persona1.seed, persona2.seed);
988 }
989
990 #[test]
991 fn test_persona_profile_different_seeds() {
992 let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
993 let persona2 = PersonaProfile::new("user456".to_string(), Domain::Finance);
994
995 assert_ne!(persona1.seed, persona2.seed);
997 }
998
999 #[test]
1000 fn test_persona_profile_traits() {
1001 let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
1002 persona.set_trait("spending_level".to_string(), "high".to_string());
1003
1004 assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
1005 assert_eq!(persona.get_trait("nonexistent"), None);
1006 }
1007
1008 #[test]
1009 fn test_persona_registry_get_or_create() {
1010 let registry = PersonaRegistry::new();
1011
1012 let persona1 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
1013 let persona2 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
1014
1015 assert_eq!(persona1.id, persona2.id);
1017 assert_eq!(persona1.seed, persona2.seed);
1018 }
1019
1020 #[test]
1021 fn test_persona_registry_default_traits() {
1022 let mut default_traits = HashMap::new();
1023 default_traits.insert("spending_level".to_string(), "high".to_string());
1024
1025 let registry = PersonaRegistry::with_default_traits(default_traits);
1026 let persona = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
1027
1028 assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
1029 }
1030
1031 #[test]
1032 fn test_persona_registry_update() {
1033 let registry = PersonaRegistry::new();
1034 registry.get_or_create_persona("user123".to_string(), Domain::Finance);
1035
1036 let mut traits = HashMap::new();
1037 traits.insert("spending_level".to_string(), "low".to_string());
1038
1039 registry.update_persona("user123", traits).unwrap();
1040
1041 let persona = registry.get_persona("user123").unwrap();
1042 assert_eq!(persona.get_trait("spending_level"), Some(&"low".to_string()));
1043 }
1044
1045 #[test]
1046 fn test_persona_generator_finance_traits() {
1047 let generator = PersonaGenerator::new(Domain::Finance);
1048 let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
1049 persona.set_trait("spending_level".to_string(), "high".to_string());
1050
1051 let value = generator.generate_for_persona(&persona, "amount").unwrap();
1053 assert!(value.is_string() || value.is_number());
1054 }
1055
1056 #[test]
1057 fn test_persona_generator_consistency() {
1058 let generator = PersonaGenerator::new(Domain::Finance);
1059 let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
1060
1061 let value1 = generator.generate_for_persona(&persona, "amount").unwrap();
1063 let value2 = generator.generate_for_persona(&persona, "amount").unwrap();
1064
1065 assert!(value1.is_string() || value1.is_number());
1068 assert!(value2.is_string() || value2.is_number());
1069 }
1070
1071 #[test]
1072 fn test_persona_backstory() {
1073 let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
1074 assert!(!persona.has_backstory());
1075 assert_eq!(persona.get_backstory(), None);
1076
1077 persona
1078 .set_backstory("High-spending finance professional with premium account".to_string());
1079 assert!(persona.has_backstory());
1080 assert!(persona.get_backstory().is_some());
1081 assert!(persona.get_backstory().unwrap().contains("High-spending"));
1082 }
1083
1084 #[test]
1085 fn test_persona_relationships() {
1086 let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
1087
1088 persona.add_relationship("owns_devices".to_string(), "device1".to_string());
1090 persona.add_relationship("owns_devices".to_string(), "device2".to_string());
1091 persona.add_relationship("belongs_to_org".to_string(), "org1".to_string());
1092
1093 let devices = persona.get_related_personas("owns_devices");
1095 assert_eq!(devices.len(), 2);
1096 assert!(devices.contains(&"device1".to_string()));
1097 assert!(devices.contains(&"device2".to_string()));
1098
1099 let orgs = persona.get_related_personas("belongs_to_org");
1100 assert_eq!(orgs.len(), 1);
1101 assert_eq!(orgs[0], "org1");
1102
1103 let types = persona.get_relationship_types();
1105 assert_eq!(types.len(), 2);
1106 assert!(types.contains(&"owns_devices".to_string()));
1107 assert!(types.contains(&"belongs_to_org".to_string()));
1108
1109 assert!(persona.remove_relationship("owns_devices", "device1"));
1111 let devices_after = persona.get_related_personas("owns_devices");
1112 assert_eq!(devices_after.len(), 1);
1113 assert_eq!(devices_after[0], "device2");
1114 }
1115
1116 #[test]
1117 fn test_persona_registry_relationships() {
1118 let registry = PersonaRegistry::new();
1119
1120 let _user = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
1122 let _device = registry.get_or_create_persona("device1".to_string(), Domain::Iot);
1123 let _org = registry.get_or_create_persona("org1".to_string(), Domain::General);
1124
1125 registry
1127 .add_relationship("user123", "owns_devices".to_string(), "device1".to_string())
1128 .unwrap();
1129 registry
1130 .add_relationship("user123", "belongs_to_org".to_string(), "org1".to_string())
1131 .unwrap();
1132
1133 let related_devices = registry.get_related_personas("user123", "owns_devices").unwrap();
1135 assert_eq!(related_devices.len(), 1);
1136 assert_eq!(related_devices[0].id, "device1");
1137
1138 let owners = registry.find_personas_with_relationship_to("device1", "owns_devices");
1140 assert_eq!(owners.len(), 1);
1141 assert_eq!(owners[0].id, "user123");
1142 }
1143}