use crate::domains::{Domain, DomainGenerator};
use crate::Result;
use rand::rngs::StdRng;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersonaProfile {
pub id: String,
pub domain: Domain,
pub traits: HashMap<String, String>,
pub seed: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backstory: Option<String>,
#[serde(default)]
pub relationships: HashMap<String, Vec<String>>,
#[serde(default)]
pub metadata: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lifecycle: Option<crate::persona_lifecycle::PersonaLifecycle>,
}
impl PersonaProfile {
pub fn new(id: String, domain: Domain) -> Self {
let seed = Self::derive_seed(&id, domain);
Self {
id,
domain,
traits: HashMap::new(),
seed,
backstory: None,
relationships: HashMap::new(),
metadata: HashMap::new(),
lifecycle: None,
}
}
pub fn with_traits(id: String, domain: Domain, traits: HashMap<String, String>) -> Self {
let mut persona = Self::new(id, domain);
persona.traits = traits;
persona
}
pub fn set_lifecycle(&mut self, lifecycle: crate::persona_lifecycle::PersonaLifecycle) {
self.lifecycle = Some(lifecycle);
}
pub fn get_lifecycle(&self) -> Option<&crate::persona_lifecycle::PersonaLifecycle> {
self.lifecycle.as_ref()
}
pub fn get_lifecycle_mut(&mut self) -> Option<&mut crate::persona_lifecycle::PersonaLifecycle> {
self.lifecycle.as_mut()
}
pub fn update_lifecycle_state(&mut self, current_time: chrono::DateTime<chrono::Utc>) {
if let Some(ref mut lifecycle) = self.lifecycle {
if let Some((new_state, _rule)) = lifecycle.transition_if_elapsed(current_time) {
lifecycle.transition_to(new_state, current_time);
let effects = lifecycle.apply_lifecycle_effects();
for (key, value) in effects {
self.set_trait(key, value);
}
}
}
}
fn derive_seed(id: &str, domain: Domain) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
id.hash(&mut hasher);
domain.as_str().hash(&mut hasher);
hasher.finish()
}
pub fn set_trait(&mut self, name: String, value: String) {
self.traits.insert(name, value);
}
pub fn get_trait(&self, name: &str) -> Option<&String> {
self.traits.get(name)
}
pub fn set_metadata(&mut self, key: String, value: Value) {
self.metadata.insert(key, value);
}
pub fn get_metadata(&self, key: &str) -> Option<&Value> {
self.metadata.get(key)
}
pub fn set_backstory(&mut self, backstory: String) {
self.backstory = Some(backstory);
}
pub fn get_backstory(&self) -> Option<&String> {
self.backstory.as_ref()
}
pub fn has_backstory(&self) -> bool {
self.backstory.is_some()
}
pub fn add_relationship(&mut self, relationship_type: String, related_persona_id: String) {
self.relationships
.entry(relationship_type)
.or_default()
.push(related_persona_id);
}
pub fn get_relationships(&self, relationship_type: &str) -> Option<&Vec<String>> {
self.relationships.get(relationship_type)
}
pub fn get_related_personas(&self, relationship_type: &str) -> Vec<String> {
self.relationships.get(relationship_type).cloned().unwrap_or_default()
}
pub fn get_relationship_types(&self) -> Vec<String> {
self.relationships.keys().cloned().collect()
}
pub fn remove_relationship(
&mut self,
relationship_type: &str,
related_persona_id: &str,
) -> bool {
if let Some(related_ids) = self.relationships.get_mut(relationship_type) {
if let Some(pos) = related_ids.iter().position(|id| id == related_persona_id) {
related_ids.remove(pos);
if related_ids.is_empty() {
self.relationships.remove(relationship_type);
}
return true;
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct PersonaRegistry {
personas: Arc<RwLock<HashMap<String, PersonaProfile>>>,
default_traits: HashMap<String, String>,
graph: Arc<crate::persona_graph::PersonaGraph>,
}
impl PersonaRegistry {
pub fn new() -> Self {
Self {
personas: Arc::new(RwLock::new(HashMap::new())),
default_traits: HashMap::new(),
graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
}
}
pub fn with_default_traits(default_traits: HashMap<String, String>) -> Self {
Self {
personas: Arc::new(RwLock::new(HashMap::new())),
default_traits,
graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
}
}
pub fn graph(&self) -> Arc<crate::persona_graph::PersonaGraph> {
Arc::clone(&self.graph)
}
pub fn get_or_create_persona(&self, id: String, domain: Domain) -> PersonaProfile {
let personas = self.personas.read().unwrap();
if let Some(persona) = personas.get(&id) {
return persona.clone();
}
drop(personas);
let mut persona = PersonaProfile::new(id.clone(), domain);
for (key, value) in &self.default_traits {
persona.set_trait(key.clone(), value.clone());
}
let mut personas = self.personas.write().unwrap();
personas.insert(id.clone(), persona.clone());
let entity_type = persona.domain.as_str().to_string();
let graph_node = crate::persona_graph::PersonaNode::new(id.clone(), entity_type);
self.graph.add_node(graph_node);
persona
}
pub fn get_persona(&self, id: &str) -> Option<PersonaProfile> {
let personas = self.personas.read().unwrap();
personas.get(id).cloned()
}
pub fn update_persona(&self, id: &str, traits: HashMap<String, String>) -> Result<()> {
let mut personas = self.personas.write().unwrap();
if let Some(persona) = personas.get_mut(id) {
for (key, value) in traits {
persona.set_trait(key, value);
}
Ok(())
} else {
Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
}
}
pub fn update_persona_backstory(&self, id: &str, backstory: String) -> Result<()> {
let mut personas = self.personas.write().unwrap();
if let Some(persona) = personas.get_mut(id) {
persona.set_backstory(backstory);
Ok(())
} else {
Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
}
}
pub fn update_persona_full(
&self,
id: &str,
traits: Option<HashMap<String, String>>,
backstory: Option<String>,
relationships: Option<HashMap<String, Vec<String>>>,
) -> Result<()> {
let mut personas = self.personas.write().unwrap();
if let Some(persona) = personas.get_mut(id) {
if let Some(traits) = traits {
for (key, value) in traits {
persona.set_trait(key, value);
}
}
if let Some(backstory) = backstory {
persona.set_backstory(backstory);
}
if let Some(relationships) = relationships {
for (rel_type, related_ids) in relationships {
for related_id in related_ids {
persona.add_relationship(rel_type.clone(), related_id);
}
}
}
Ok(())
} else {
Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
}
}
pub fn remove_persona(&self, id: &str) -> bool {
let mut personas = self.personas.write().unwrap();
personas.remove(id).is_some()
}
pub fn list_persona_ids(&self) -> Vec<String> {
let personas = self.personas.read().unwrap();
personas.keys().cloned().collect()
}
pub fn clear(&self) {
let mut personas = self.personas.write().unwrap();
personas.clear();
}
pub fn count(&self) -> usize {
let personas = self.personas.read().unwrap();
personas.len()
}
pub fn get_related_personas(
&self,
persona_id: &str,
relationship_type: &str,
) -> Result<Vec<PersonaProfile>> {
let personas = self.personas.read().unwrap();
if let Some(persona) = personas.get(persona_id) {
let related_ids = persona.get_related_personas(relationship_type);
let mut related_personas = Vec::new();
for related_id in related_ids {
if let Some(related_persona) = personas.get(&related_id) {
related_personas.push(related_persona.clone());
}
}
Ok(related_personas)
} else {
Err(crate::Error::generic(format!("Persona with ID '{}' not found", persona_id)))
}
}
pub fn find_personas_with_relationship_to(
&self,
target_persona_id: &str,
relationship_type: &str,
) -> Vec<PersonaProfile> {
let personas = self.personas.read().unwrap();
let mut result = Vec::new();
for persona in personas.values() {
if let Some(related_ids) = persona.get_relationships(relationship_type) {
if related_ids.contains(&target_persona_id.to_string()) {
result.push(persona.clone());
}
}
}
result
}
pub fn add_relationship(
&self,
from_persona_id: &str,
relationship_type: String,
to_persona_id: String,
) -> Result<()> {
let mut personas = self.personas.write().unwrap();
if let Some(persona) = personas.get_mut(from_persona_id) {
persona.add_relationship(relationship_type.clone(), to_persona_id.clone());
self.graph
.add_edge(from_persona_id.to_string(), to_persona_id, relationship_type);
Ok(())
} else {
Err(crate::Error::generic(format!(
"Persona with ID '{}' not found",
from_persona_id
)))
}
}
pub fn coherent_persona_switch<F>(
&self,
root_persona_id: &str,
relationship_types: Option<&[String]>,
update_callback: Option<F>,
) -> Result<Vec<String>>
where
F: Fn(&str, &mut PersonaProfile),
{
let related_ids = self.graph.find_related_bfs(root_persona_id, relationship_types, None);
let mut updated_ids = vec![root_persona_id.to_string()];
updated_ids.extend(related_ids);
let mut personas = self.personas.write().unwrap();
for persona_id in &updated_ids {
if let Some(persona) = personas.get_mut(persona_id) {
if let Some(ref callback) = update_callback {
callback(persona_id, persona);
}
}
}
Ok(updated_ids)
}
}
impl Default for PersonaRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct PersonaGenerator {
domain_generator: DomainGenerator,
}
impl PersonaGenerator {
pub fn new(domain: Domain) -> Self {
Self {
domain_generator: DomainGenerator::new(domain),
}
}
pub fn generate_for_persona(
&self,
persona: &PersonaProfile,
field_type: &str,
) -> Result<Value> {
self.generate_for_persona_with_reality(persona, field_type, 0.0, None, None)
}
pub fn generate_for_persona_with_reality(
&self,
persona: &PersonaProfile,
field_type: &str,
reality_ratio: f64,
recorded_data: Option<&Value>,
real_data: Option<&Value>,
) -> Result<Value> {
use rand::rngs::StdRng;
use rand::SeedableRng;
let mut rng = StdRng::seed_from_u64(persona.seed);
let mut synthetic_value = self.domain_generator.generate(field_type)?;
synthetic_value =
self.apply_persona_traits(persona, field_type, synthetic_value, &mut rng)?;
let reality_ratio = reality_ratio.clamp(0.0, 1.0);
if reality_ratio < 0.3 {
Ok(synthetic_value)
} else if reality_ratio < 0.7 {
if let Some(recorded) = recorded_data {
self.blend_values(&synthetic_value, recorded, reality_ratio)
} else {
Ok(synthetic_value)
}
} else {
if let Some(real) = real_data {
self.blend_values(&synthetic_value, real, reality_ratio)
} else if let Some(recorded) = recorded_data {
self.blend_values(&synthetic_value, recorded, reality_ratio)
} else {
Ok(synthetic_value)
}
}
}
fn blend_values(&self, synthetic: &Value, other: &Value, ratio: f64) -> Result<Value> {
match (synthetic, other) {
(Value::Number(syn_num), Value::Number(other_num)) => {
if let (Some(syn_f64), Some(other_f64)) = (syn_num.as_f64(), other_num.as_f64()) {
let adjusted_ratio = if ratio < 0.7 {
(ratio - 0.3) / 0.4
} else {
(ratio - 0.7) / 0.3
};
let blended = syn_f64 * (1.0 - adjusted_ratio) + other_f64 * adjusted_ratio;
Ok(Value::Number(
serde_json::Number::from_f64(blended).unwrap_or(syn_num.clone()),
))
} else {
Ok(synthetic.clone())
}
}
(Value::String(_), Value::String(other_str)) => {
let adjusted_ratio = if ratio < 0.7 {
(ratio - 0.3) / 0.4
} else {
(ratio - 0.7) / 0.3
};
if adjusted_ratio >= 0.5 {
Ok(Value::String(other_str.clone()))
} else {
Ok(synthetic.clone())
}
}
(Value::Bool(_), Value::Bool(other_bool)) => {
let adjusted_ratio = if ratio < 0.7 {
(ratio - 0.3) / 0.4
} else {
(ratio - 0.7) / 0.3
};
if adjusted_ratio >= 0.5 {
Ok(Value::Bool(*other_bool))
} else {
Ok(synthetic.clone())
}
}
_ => {
let adjusted_ratio = if ratio < 0.7 {
(ratio - 0.3) / 0.4
} else {
(ratio - 0.7) / 0.3
};
if adjusted_ratio >= 0.5 {
Ok(other.clone())
} else {
Ok(synthetic.clone())
}
}
}
}
pub fn generate_traits_from_backstory(
&self,
persona: &PersonaProfile,
) -> Result<HashMap<String, String>> {
let mut inferred_traits = HashMap::new();
let backstory = match persona.get_backstory() {
Some(bs) => bs,
None => return Ok(inferred_traits),
};
let backstory_lower = backstory.to_lowercase();
match persona.domain {
Domain::Finance => {
if backstory_lower.contains("high-spending")
|| backstory_lower.contains("high spending")
|| backstory_lower.contains("big spender")
{
inferred_traits.insert("spending_level".to_string(), "high".to_string());
} else if backstory_lower.contains("conservative")
|| backstory_lower.contains("low spending")
|| backstory_lower.contains("frugal")
{
inferred_traits
.insert("spending_level".to_string(), "conservative".to_string());
} else if backstory_lower.contains("moderate") {
inferred_traits.insert("spending_level".to_string(), "moderate".to_string());
}
if backstory_lower.contains("premium") {
inferred_traits.insert("account_type".to_string(), "premium".to_string());
} else if backstory_lower.contains("business") {
inferred_traits.insert("account_type".to_string(), "business".to_string());
} else if backstory_lower.contains("savings") {
inferred_traits.insert("account_type".to_string(), "savings".to_string());
} else if backstory_lower.contains("checking") {
inferred_traits.insert("account_type".to_string(), "checking".to_string());
}
let currencies = ["usd", "eur", "gbp", "jpy", "cny"];
for currency in ¤cies {
if backstory_lower.contains(currency) {
inferred_traits
.insert("preferred_currency".to_string(), currency.to_uppercase());
break;
}
}
if backstory_lower.contains("long-term") || backstory_lower.contains("long term") {
inferred_traits.insert("account_age".to_string(), "long_term".to_string());
} else if backstory_lower.contains("established") {
inferred_traits.insert("account_age".to_string(), "established".to_string());
} else if backstory_lower.contains("new") {
inferred_traits.insert("account_age".to_string(), "new".to_string());
}
}
Domain::Ecommerce => {
if backstory_lower.contains("vip") {
inferred_traits.insert("customer_segment".to_string(), "VIP".to_string());
} else if backstory_lower.contains("new") {
inferred_traits.insert("customer_segment".to_string(), "new".to_string());
} else {
inferred_traits.insert("customer_segment".to_string(), "regular".to_string());
}
if backstory_lower.contains("frequent") {
inferred_traits
.insert("purchase_frequency".to_string(), "frequent".to_string());
} else if backstory_lower.contains("occasional") {
inferred_traits
.insert("purchase_frequency".to_string(), "occasional".to_string());
} else if backstory_lower.contains("regular") {
inferred_traits.insert("purchase_frequency".to_string(), "regular".to_string());
}
let categories = ["electronics", "clothing", "books", "home", "sports"];
for category in &categories {
if backstory_lower.contains(category) {
inferred_traits
.insert("preferred_category".to_string(), category.to_string());
break;
}
}
if backstory_lower.contains("express") || backstory_lower.contains("overnight") {
inferred_traits.insert("preferred_shipping".to_string(), "express".to_string());
} else if backstory_lower.contains("standard") {
inferred_traits
.insert("preferred_shipping".to_string(), "standard".to_string());
}
}
Domain::Healthcare => {
if backstory_lower.contains("private") {
inferred_traits.insert("insurance_type".to_string(), "private".to_string());
} else if backstory_lower.contains("medicare") {
inferred_traits.insert("insurance_type".to_string(), "medicare".to_string());
} else if backstory_lower.contains("medicaid") {
inferred_traits.insert("insurance_type".to_string(), "medicaid".to_string());
} else if backstory_lower.contains("uninsured") {
inferred_traits.insert("insurance_type".to_string(), "uninsured".to_string());
}
let blood_types = ["a+", "a-", "b+", "b-", "ab+", "ab-", "o+", "o-"];
for blood_type in &blood_types {
if backstory_lower.contains(blood_type) {
inferred_traits.insert("blood_type".to_string(), blood_type.to_uppercase());
break;
}
}
if backstory_lower.contains("pediatric") || backstory_lower.contains("child") {
inferred_traits.insert("age_group".to_string(), "pediatric".to_string());
} else if backstory_lower.contains("senior") || backstory_lower.contains("elderly")
{
inferred_traits.insert("age_group".to_string(), "senior".to_string());
} else {
inferred_traits.insert("age_group".to_string(), "adult".to_string());
}
if backstory_lower.contains("frequent") {
inferred_traits.insert("visit_frequency".to_string(), "frequent".to_string());
} else if backstory_lower.contains("regular") {
inferred_traits.insert("visit_frequency".to_string(), "regular".to_string());
} else if backstory_lower.contains("occasional") {
inferred_traits.insert("visit_frequency".to_string(), "occasional".to_string());
} else if backstory_lower.contains("rare") {
inferred_traits.insert("visit_frequency".to_string(), "rare".to_string());
}
if backstory_lower.contains("multiple") || backstory_lower.contains("several") {
inferred_traits
.insert("chronic_conditions".to_string(), "multiple".to_string());
} else if backstory_lower.contains("single") || backstory_lower.contains("one") {
inferred_traits.insert("chronic_conditions".to_string(), "single".to_string());
} else if backstory_lower.contains("none")
|| backstory_lower.contains("no conditions")
{
inferred_traits.insert("chronic_conditions".to_string(), "none".to_string());
}
}
_ => {
}
}
Ok(inferred_traits)
}
fn apply_persona_traits(
&self,
persona: &PersonaProfile,
field_type: &str,
value: Value,
_rng: &mut StdRng,
) -> Result<Value> {
let mut effective_persona = persona.clone();
if persona.has_backstory() && persona.traits.is_empty() {
if let Ok(inferred_traits) = self.generate_traits_from_backstory(persona) {
for (key, val) in inferred_traits {
effective_persona.set_trait(key, val);
}
}
}
match effective_persona.domain {
Domain::Finance => self.apply_finance_traits(&effective_persona, field_type, value),
Domain::Ecommerce => self.apply_ecommerce_traits(&effective_persona, field_type, value),
Domain::Healthcare => {
self.apply_healthcare_traits(&effective_persona, field_type, value)
}
_ => Ok(value), }
}
fn apply_finance_traits(
&self,
persona: &PersonaProfile,
field_type: &str,
value: Value,
) -> Result<Value> {
match field_type {
"amount" | "balance" | "transaction_amount" => {
if let Some(spending_level) = persona.get_trait("spending_level") {
let multiplier = match spending_level.as_str() {
"high" => 2.0,
"moderate" => 1.0,
"conservative" | "low" => 0.5,
_ => 1.0,
};
if let Some(num) = value.as_f64() {
return Ok(Value::Number(
serde_json::Number::from_f64(num * multiplier)
.unwrap_or_else(|| serde_json::Number::from(0)),
));
}
}
Ok(value)
}
"currency" => {
if let Some(currency) = persona.get_trait("preferred_currency") {
return Ok(Value::String(currency.clone()));
}
Ok(value)
}
"account_type" => {
if let Some(account_type) = persona.get_trait("account_type") {
return Ok(Value::String(account_type.clone()));
}
Ok(value)
}
_ => Ok(value),
}
}
fn apply_ecommerce_traits(
&self,
persona: &PersonaProfile,
field_type: &str,
value: Value,
) -> Result<Value> {
match field_type {
"price" | "order_total" => {
if let Some(segment) = persona.get_trait("customer_segment") {
let multiplier = match segment.as_str() {
"VIP" => 1.5,
"regular" => 1.0,
"new" => 0.7,
_ => 1.0,
};
if let Some(num) = value.as_f64() {
return Ok(Value::Number(
serde_json::Number::from_f64(num * multiplier)
.unwrap_or_else(|| serde_json::Number::from(0)),
));
}
}
Ok(value)
}
"shipping_method" => {
if let Some(shipping) = persona.get_trait("preferred_shipping") {
return Ok(Value::String(shipping.clone()));
}
Ok(value)
}
_ => Ok(value),
}
}
fn apply_healthcare_traits(
&self,
persona: &PersonaProfile,
field_type: &str,
value: Value,
) -> Result<Value> {
match field_type {
"insurance_type" => {
if let Some(insurance) = persona.get_trait("insurance_type") {
return Ok(Value::String(insurance.clone()));
}
Ok(value)
}
"blood_type" => {
if let Some(blood_type) = persona.get_trait("blood_type") {
return Ok(Value::String(blood_type.clone()));
}
Ok(value)
}
_ => Ok(value),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_persona_profile_new() {
let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
assert_eq!(persona.id, "user123");
assert_eq!(persona.domain, Domain::Finance);
assert!(persona.traits.is_empty());
assert!(persona.seed > 0);
}
#[test]
fn test_persona_profile_deterministic_seed() {
let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
let persona2 = PersonaProfile::new("user123".to_string(), Domain::Finance);
assert_eq!(persona1.seed, persona2.seed);
}
#[test]
fn test_persona_profile_different_seeds() {
let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
let persona2 = PersonaProfile::new("user456".to_string(), Domain::Finance);
assert_ne!(persona1.seed, persona2.seed);
}
#[test]
fn test_persona_profile_traits() {
let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
persona.set_trait("spending_level".to_string(), "high".to_string());
assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
assert_eq!(persona.get_trait("nonexistent"), None);
}
#[test]
fn test_persona_registry_get_or_create() {
let registry = PersonaRegistry::new();
let persona1 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
let persona2 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
assert_eq!(persona1.id, persona2.id);
assert_eq!(persona1.seed, persona2.seed);
}
#[test]
fn test_persona_registry_default_traits() {
let mut default_traits = HashMap::new();
default_traits.insert("spending_level".to_string(), "high".to_string());
let registry = PersonaRegistry::with_default_traits(default_traits);
let persona = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
}
#[test]
fn test_persona_registry_update() {
let registry = PersonaRegistry::new();
registry.get_or_create_persona("user123".to_string(), Domain::Finance);
let mut traits = HashMap::new();
traits.insert("spending_level".to_string(), "low".to_string());
registry.update_persona("user123", traits).unwrap();
let persona = registry.get_persona("user123").unwrap();
assert_eq!(persona.get_trait("spending_level"), Some(&"low".to_string()));
}
#[test]
fn test_persona_generator_finance_traits() {
let generator = PersonaGenerator::new(Domain::Finance);
let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
persona.set_trait("spending_level".to_string(), "high".to_string());
let value = generator.generate_for_persona(&persona, "amount").unwrap();
assert!(value.is_string() || value.is_number());
}
#[test]
fn test_persona_generator_consistency() {
let generator = PersonaGenerator::new(Domain::Finance);
let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
let value1 = generator.generate_for_persona(&persona, "amount").unwrap();
let value2 = generator.generate_for_persona(&persona, "amount").unwrap();
assert!(value1.is_string() || value1.is_number());
assert!(value2.is_string() || value2.is_number());
}
#[test]
fn test_persona_backstory() {
let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
assert!(!persona.has_backstory());
assert_eq!(persona.get_backstory(), None);
persona
.set_backstory("High-spending finance professional with premium account".to_string());
assert!(persona.has_backstory());
assert!(persona.get_backstory().is_some());
assert!(persona.get_backstory().unwrap().contains("High-spending"));
}
#[test]
fn test_persona_relationships() {
let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
persona.add_relationship("owns_devices".to_string(), "device1".to_string());
persona.add_relationship("owns_devices".to_string(), "device2".to_string());
persona.add_relationship("belongs_to_org".to_string(), "org1".to_string());
let devices = persona.get_related_personas("owns_devices");
assert_eq!(devices.len(), 2);
assert!(devices.contains(&"device1".to_string()));
assert!(devices.contains(&"device2".to_string()));
let orgs = persona.get_related_personas("belongs_to_org");
assert_eq!(orgs.len(), 1);
assert_eq!(orgs[0], "org1");
let types = persona.get_relationship_types();
assert_eq!(types.len(), 2);
assert!(types.contains(&"owns_devices".to_string()));
assert!(types.contains(&"belongs_to_org".to_string()));
assert!(persona.remove_relationship("owns_devices", "device1"));
let devices_after = persona.get_related_personas("owns_devices");
assert_eq!(devices_after.len(), 1);
assert_eq!(devices_after[0], "device2");
}
#[test]
fn test_persona_registry_relationships() {
let registry = PersonaRegistry::new();
let _user = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
let _device = registry.get_or_create_persona("device1".to_string(), Domain::Iot);
let _org = registry.get_or_create_persona("org1".to_string(), Domain::General);
registry
.add_relationship("user123", "owns_devices".to_string(), "device1".to_string())
.unwrap();
registry
.add_relationship("user123", "belongs_to_org".to_string(), "org1".to_string())
.unwrap();
let related_devices = registry.get_related_personas("user123", "owns_devices").unwrap();
assert_eq!(related_devices.len(), 1);
assert_eq!(related_devices[0].id, "device1");
let owners = registry.find_personas_with_relationship_to("device1", "owns_devices");
assert_eq!(owners.len(), 1);
assert_eq!(owners[0].id, "user123");
}
}