use crate::context::EcologicalContext;
use crate::enums::{LifeStage, PersonalityProfile, Species};
use crate::state::{
Disposition, Hexaco, IndividualState, MentalHealth, Mood, Needs, PersonCharacteristics,
SocialCognition,
};
use crate::types::{Duration, EntityId, Timestamp};
use super::Entity;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntityBuildError {
MissingSpecies,
MissingAge,
InvalidId(String),
}
impl std::fmt::Display for EntityBuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EntityBuildError::MissingSpecies => write!(f, "Species is required but was not set"),
EntityBuildError::MissingAge => write!(f, "Age is required but was not set"),
EntityBuildError::InvalidId(reason) => write!(f, "Invalid entity ID: {}", reason),
}
}
}
impl std::error::Error for EntityBuildError {}
#[derive(Debug, Clone, Default)]
pub struct EntityBuilder {
id: Option<String>,
species: Option<Species>,
age: Option<Duration>,
birth_date: Option<Timestamp>,
life_stage: Option<LifeStage>,
personality: Option<PersonalityProfile>,
hexaco: Option<Hexaco>,
person_characteristics: Option<PersonCharacteristics>,
mood: Option<Mood>,
needs: Option<Needs>,
mental_health: Option<MentalHealth>,
social_cognition: Option<SocialCognition>,
disposition: Option<Disposition>,
context: Option<EcologicalContext>,
}
impl EntityBuilder {
#[must_use]
pub fn new() -> Self {
EntityBuilder::default()
}
#[must_use]
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
#[must_use]
pub fn species(mut self, species: Species) -> Self {
self.species = Some(species);
self
}
#[must_use]
pub fn age(mut self, age: Duration) -> Self {
self.age = Some(age);
self
}
#[must_use]
pub fn birth_date(mut self, birth_date: Timestamp) -> Self {
self.birth_date = Some(birth_date);
self
}
#[must_use]
pub fn life_stage(mut self, stage: LifeStage) -> Self {
self.life_stage = Some(stage);
self
}
#[must_use]
pub fn personality(mut self, profile: PersonalityProfile) -> Self {
self.personality = Some(profile);
self.hexaco = None; self
}
#[must_use]
pub fn hexaco(mut self, hexaco: Hexaco) -> Self {
self.hexaco = Some(hexaco);
self.personality = None; self
}
#[must_use]
pub fn person_characteristics(mut self, pc: PersonCharacteristics) -> Self {
self.person_characteristics = Some(pc);
self
}
#[must_use]
pub fn mood(mut self, mood: Mood) -> Self {
self.mood = Some(mood);
self
}
#[must_use]
pub fn needs(mut self, needs: Needs) -> Self {
self.needs = Some(needs);
self
}
#[must_use]
pub fn mental_health(mut self, mental_health: MentalHealth) -> Self {
self.mental_health = Some(mental_health);
self
}
#[must_use]
pub fn social_cognition(mut self, social_cognition: SocialCognition) -> Self {
self.social_cognition = Some(social_cognition);
self
}
#[must_use]
pub fn disposition(mut self, disposition: Disposition) -> Self {
self.disposition = Some(disposition);
self
}
#[must_use]
pub fn with_context(mut self, context: EcologicalContext) -> Self {
self.context = Some(context);
self
}
pub fn build(self) -> Result<Entity, EntityBuildError> {
let species = self.species.ok_or(EntityBuildError::MissingSpecies)?;
let age = self.age.ok_or(EntityBuildError::MissingAge)?;
let id_string = self.id.unwrap_or_else(generate_uuid);
let id = match EntityId::new(id_string) {
Ok(id) => id,
Err(err) => return Err(EntityBuildError::InvalidId(err.reason)),
};
let birth_date = self.birth_date;
let life_stage = self
.life_stage
.unwrap_or_else(|| LifeStage::from_age_years_for_species(&species, age.as_years_f64()));
let hexaco = if let Some(h) = self.hexaco {
h
} else if let Some(profile) = self.personality {
Hexaco::from_profile(profile)
} else {
Hexaco::from_profile(PersonalityProfile::Balanced)
};
let person_characteristics = self.person_characteristics.unwrap_or_default();
let mut individual_state = IndividualState::new()
.with_hexaco(hexaco.clone())
.with_person_characteristics(person_characteristics);
if let Some(mood) = self.mood {
individual_state = individual_state.with_mood(mood);
} else {
individual_state = individual_state.with_mood(Mood::from_personality(&hexaco));
}
if let Some(needs) = self.needs {
individual_state = individual_state.with_needs(needs);
}
if let Some(mental_health) = self.mental_health {
individual_state = individual_state.with_mental_health(mental_health);
}
if let Some(social_cognition) = self.social_cognition {
individual_state = individual_state.with_social_cognition(social_cognition);
}
if let Some(disposition) = self.disposition {
individual_state = individual_state.with_disposition(disposition);
}
if let Some(context) = self.context {
Ok(Entity::new_with_context(
id,
species,
age,
birth_date,
life_stage,
individual_state,
context,
))
} else {
Ok(Entity::new(
id,
species,
age,
birth_date,
life_stage,
individual_state,
))
}
}
}
fn generate_uuid() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
let count = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("entity_{:016x}", count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_sets_species() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.species(), &Species::Human);
}
#[test]
fn builder_sets_life_stage() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.life_stage(LifeStage::Adult)
.build()
.unwrap();
assert_eq!(entity.life_stage(), LifeStage::Adult);
}
#[test]
fn builder_sets_personality_profile() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.personality(PersonalityProfile::Leader)
.build()
.unwrap();
assert!(entity.individual_state().hexaco().extraversion() > 0.5);
}
#[test]
fn builder_sets_person_characteristics() {
let pc = PersonCharacteristics::new().with_cognitive_ability_base(0.9);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.person_characteristics(pc)
.build()
.unwrap();
assert!(
(entity
.individual_state()
.person_characteristics()
.cognitive_ability()
.base()
- 0.9)
.abs()
< f32::EPSILON
);
}
#[test]
fn builder_produces_entity() {
let result = EntityBuilder::new().species(Species::Human).age(crate::types::Duration::years(30)).build();
assert!(result.is_ok());
let entity = result.unwrap();
assert_eq!(entity.species(), &Species::Human);
}
#[test]
fn builder_requires_species() {
let result = EntityBuilder::new().build();
assert_eq!(result.unwrap_err(), EntityBuildError::MissingSpecies);
}
#[test]
fn builder_defaults_life_stage() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.life_stage(), LifeStage::YoungAdult);
let adult = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(40))
.build()
.unwrap();
assert_eq!(adult.life_stage(), LifeStage::Adult);
let child = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(8))
.build()
.unwrap();
assert_eq!(child.life_stage(), LifeStage::Child);
}
#[test]
fn builder_sets_id() {
let entity = EntityBuilder::new()
.id("test_entity")
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.id().as_str(), "test_entity");
}
#[test]
fn builder_generates_id_if_not_set() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert!(!entity.id().as_str().is_empty());
assert!(entity.id().as_str().starts_with("entity_"));
}
#[test]
fn builder_sets_age() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(25))
.build()
.unwrap();
assert_eq!(entity.age().as_years(), 25);
}
#[test]
fn builder_requires_age() {
let result = EntityBuilder::new().species(Species::Human).build();
assert_eq!(result.unwrap_err(), EntityBuildError::MissingAge);
}
#[test]
fn builder_sets_hexaco_directly() {
let hexaco = Hexaco::new().with_openness(0.9).with_neuroticism(-0.8);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.hexaco(hexaco)
.build()
.unwrap();
assert!((entity.individual_state().hexaco().openness() - 0.9).abs() < f32::EPSILON);
assert!((entity.individual_state().hexaco().neuroticism() - (-0.8)).abs() < f32::EPSILON);
}
#[test]
fn hexaco_overrides_personality() {
let hexaco = Hexaco::uniform(0.3);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.personality(PersonalityProfile::Leader) .hexaco(hexaco) .build()
.unwrap();
assert!((entity.individual_state().hexaco().extraversion() - 0.3).abs() < f32::EPSILON);
}
#[test]
fn personality_overrides_hexaco() {
let hexaco = Hexaco::uniform(0.3);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.hexaco(hexaco)
.personality(PersonalityProfile::Leader) .build()
.unwrap();
assert!(entity.individual_state().hexaco().extraversion() > 0.5);
}
#[test]
fn builder_clone() {
let builder = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(30));
let cloned = builder.clone();
let entity1 = builder.build().unwrap();
let entity2 = cloned.build().unwrap();
assert_eq!(entity1.species(), entity2.species());
assert_eq!(entity1.age(), entity2.age());
}
#[test]
fn empty_id_returns_error() {
let result = EntityBuilder::new().id("").species(Species::Human).age(crate::types::Duration::years(30)).build();
assert_eq!(
result.unwrap_err(),
EntityBuildError::InvalidId("ID cannot be empty".to_string())
);
}
#[test]
fn builder_debug() {
let builder = EntityBuilder::new().species(Species::Human);
let debug = format!("{:?}", builder);
assert!(debug.contains("EntityBuilder"));
}
#[test]
fn error_display() {
let err = EntityBuildError::MissingSpecies;
let display = format!("{}", err);
assert!(display.contains("Species"));
let err_age = EntityBuildError::MissingAge;
let display_age = format!("{}", err_age);
assert!(display_age.contains("Age"));
let err2 = EntityBuildError::InvalidId("test reason".to_string());
let display2 = format!("{}", err2);
assert!(display2.contains("test reason"));
}
#[test]
fn error_debug() {
let err = EntityBuildError::MissingSpecies;
let debug = format!("{:?}", err);
assert!(debug.contains("MissingSpecies"));
let err_age = EntityBuildError::MissingAge;
let debug_age = format!("{:?}", err_age);
assert!(debug_age.contains("MissingAge"));
}
#[test]
fn error_is_std_error() {
use std::error::Error;
let err: &dyn Error = &EntityBuildError::MissingSpecies;
assert!(err.source().is_none());
let err_age: &dyn Error = &EntityBuildError::MissingAge;
assert!(err_age.source().is_none());
let err2: &dyn Error = &EntityBuildError::InvalidId("test".to_string());
assert!(err2.source().is_none());
}
#[test]
fn builder_new() {
let builder = EntityBuilder::new();
let debug = format!("{:?}", builder);
assert!(debug.contains("EntityBuilder"));
}
#[test]
fn dog_age_derives_life_stage() {
let dog = EntityBuilder::new()
.species(Species::Dog)
.age(Duration::years(2))
.build()
.unwrap();
assert_eq!(dog.life_stage(), LifeStage::YoungAdult);
}
#[test]
fn explicit_life_stage_overrides_age_derived() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(Duration::years(30)) .life_stage(LifeStage::Elder) .build()
.unwrap();
assert_eq!(entity.life_stage(), LifeStage::Elder);
}
#[test]
fn default_personality_is_balanced() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert!((entity.individual_state().hexaco().openness() - 0.0).abs() < 0.01);
}
#[test]
fn default_person_characteristics_are_neutral() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
let pc = entity.individual_state().person_characteristics();
assert!(pc.resource() >= 0.3 && pc.resource() <= 0.7);
assert!(pc.force() >= 0.3 && pc.force() <= 0.7);
}
#[test]
fn builder_with_context() {
use crate::context::{Microsystem, WorkContext};
use crate::types::MicrosystemId;
let mut context = EcologicalContext::default();
let work_id = MicrosystemId::new("work_primary").unwrap();
context.add_microsystem(work_id, Microsystem::new_work(WorkContext::default()));
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.with_context(context)
.build()
.unwrap();
assert_eq!(entity.context().microsystem_count(), 1);
}
#[test]
fn builder_without_context_uses_default() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.context().microsystem_count(), 0);
}
#[test]
fn builder_sets_birth_date() {
let birth = Timestamp::from_ymd_hms(1990, 6, 15, 0, 0, 0);
let entity = EntityBuilder::new()
.id("person_001")
.species(Species::Human)
.age(crate::types::Duration::years(30))
.birth_date(birth)
.build()
.unwrap();
assert_eq!(entity.birth_date(), Some(birth));
}
#[test]
fn builder_without_birth_date_returns_none() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert!(entity.birth_date().is_none());
}
#[test]
fn builder_birth_date_can_be_used_with_age() {
let birth = Timestamp::from_ymd_hms(1990, 6, 15, 0, 0, 0);
let entity = EntityBuilder::new()
.species(Species::Human)
.birth_date(birth)
.age(Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.birth_date(), Some(birth));
assert_eq!(entity.age().as_years(), 30);
}
#[test]
fn builder_sets_mood() {
use crate::state::Mood;
let mood = Mood::new().with_valence_base(0.6).with_arousal_base(-0.3);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.mood(mood.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().mood(), &mood);
}
#[test]
fn builder_sets_needs() {
use crate::state::Needs;
let needs = Needs::new().with_fatigue_base(0.4).with_stress_base(0.2);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.needs(needs.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().needs(), &needs);
}
#[test]
fn builder_sets_mental_health() {
use crate::state::MentalHealth;
let mh = MentalHealth::new()
.with_depression_base(0.15)
.with_hopelessness_base(0.1);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.mental_health(mh.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().mental_health(), &mh);
}
#[test]
fn builder_sets_social_cognition() {
use crate::state::SocialCognition;
let sc = SocialCognition::new()
.with_loneliness_base(0.25)
.with_perceived_reciprocal_caring_base(0.7);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.social_cognition(sc.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().social_cognition(), &sc);
}
#[test]
fn builder_sets_disposition() {
use crate::state::Disposition;
let disp = Disposition::new()
.with_empathy_base(0.8)
.with_impulse_control_base(0.7);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.disposition(disp.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().disposition(), &disp);
}
#[test]
fn builder_without_optional_state_uses_defaults() {
use crate::state::{Disposition, MentalHealth, Needs, SocialCognition};
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.build()
.unwrap();
assert_eq!(entity.individual_state().needs(), &Needs::default());
assert_eq!(
entity.individual_state().mental_health(),
&MentalHealth::default()
);
assert_eq!(
entity.individual_state().social_cognition(),
&SocialCognition::default()
);
assert_eq!(
entity.individual_state().disposition(),
&Disposition::default()
);
}
#[test]
fn builder_derives_mood_from_personality() {
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.personality(PersonalityProfile::Leader) .build()
.unwrap();
let valence = entity.individual_state().mood().valence_base();
assert!(valence > 0.0);
}
#[test]
fn explicit_mood_overrides_personality_derived() {
use crate::state::Mood;
let explicit_mood = Mood::new()
.with_valence_base(-0.5) .with_arousal_base(0.3);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.personality(PersonalityProfile::Leader) .mood(explicit_mood.clone()) .build()
.unwrap();
assert_eq!(entity.individual_state().mood(), &explicit_mood);
assert!((entity.individual_state().mood().valence_base() - (-0.5)).abs() < f32::EPSILON);
}
#[test]
fn builder_sets_all_state_components_together() {
use crate::state::{Disposition, MentalHealth, Mood, Needs, SocialCognition};
let mood = Mood::new().with_valence_base(0.5);
let needs = Needs::new().with_purpose_base(0.8);
let mh = MentalHealth::new().with_depression_base(0.1);
let sc = SocialCognition::new().with_loneliness_base(0.2);
let disp = Disposition::new().with_empathy_base(0.9);
let entity = EntityBuilder::new()
.species(Species::Human)
.age(crate::types::Duration::years(30))
.mood(mood.clone())
.needs(needs.clone())
.mental_health(mh.clone())
.social_cognition(sc.clone())
.disposition(disp.clone())
.build()
.unwrap();
assert_eq!(entity.individual_state().mood(), &mood);
assert_eq!(entity.individual_state().needs(), &needs);
assert_eq!(entity.individual_state().mental_health(), &mh);
assert_eq!(entity.individual_state().social_cognition(), &sc);
assert_eq!(entity.individual_state().disposition(), &disp);
}
}