1use crate::context::EcologicalContext;
7use crate::enums::{LifeStage, PersonalityProfile, Species};
8use crate::state::{
9 Disposition, Hexaco, IndividualState, MentalHealth, Mood, Needs, PersonCharacteristics,
10 SocialCognition,
11};
12use crate::types::{Duration, EntityId, Timestamp};
14
15use super::Entity;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum EntityBuildError {
23 MissingSpecies,
25
26 MissingAge,
28
29 InvalidId(String),
31}
32
33impl std::fmt::Display for EntityBuildError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 EntityBuildError::MissingSpecies => write!(f, "Species is required but was not set"),
37 EntityBuildError::MissingAge => write!(f, "Age is required but was not set"),
38 EntityBuildError::InvalidId(reason) => write!(f, "Invalid entity ID: {}", reason),
39 }
40 }
41}
42
43impl std::error::Error for EntityBuildError {}
44
45#[derive(Debug, Clone, Default)]
63pub struct EntityBuilder {
64 id: Option<String>,
65 species: Option<Species>,
66 age: Option<Duration>,
67 birth_date: Option<Timestamp>,
68 life_stage: Option<LifeStage>,
69 personality: Option<PersonalityProfile>,
70 hexaco: Option<Hexaco>,
71 person_characteristics: Option<PersonCharacteristics>,
72 mood: Option<Mood>,
73 needs: Option<Needs>,
74 mental_health: Option<MentalHealth>,
75 social_cognition: Option<SocialCognition>,
76 disposition: Option<Disposition>,
77 context: Option<EcologicalContext>,
78}
79
80impl EntityBuilder {
81 #[must_use]
84 pub fn new() -> Self {
85 EntityBuilder::default()
86 }
87
88 #[must_use]
93 pub fn id(mut self, id: impl Into<String>) -> Self {
94 self.id = Some(id.into());
95 self
96 }
97
98 #[must_use]
104 pub fn species(mut self, species: Species) -> Self {
105 self.species = Some(species);
106 self
107 }
108
109 #[must_use]
115 pub fn age(mut self, age: Duration) -> Self {
116 self.age = Some(age);
117 self
118 }
119
120 #[must_use]
130 pub fn birth_date(mut self, birth_date: Timestamp) -> Self {
131 self.birth_date = Some(birth_date);
132 self
133 }
134
135 #[must_use]
140 pub fn life_stage(mut self, stage: LifeStage) -> Self {
141 self.life_stage = Some(stage);
142 self
143 }
144
145 #[must_use]
151 pub fn personality(mut self, profile: PersonalityProfile) -> Self {
152 self.personality = Some(profile);
153 self.hexaco = None; self
155 }
156
157 #[must_use]
163 pub fn hexaco(mut self, hexaco: Hexaco) -> Self {
164 self.hexaco = Some(hexaco);
165 self.personality = None; self
167 }
168
169 #[must_use]
175 pub fn person_characteristics(mut self, pc: PersonCharacteristics) -> Self {
176 self.person_characteristics = Some(pc);
177 self
178 }
179
180 #[must_use]
185 pub fn mood(mut self, mood: Mood) -> Self {
186 self.mood = Some(mood);
187 self
188 }
189
190 #[must_use]
195 pub fn needs(mut self, needs: Needs) -> Self {
196 self.needs = Some(needs);
197 self
198 }
199
200 #[must_use]
206 pub fn mental_health(mut self, mental_health: MentalHealth) -> Self {
207 self.mental_health = Some(mental_health);
208 self
209 }
210
211 #[must_use]
217 pub fn social_cognition(mut self, social_cognition: SocialCognition) -> Self {
218 self.social_cognition = Some(social_cognition);
219 self
220 }
221
222 #[must_use]
228 pub fn disposition(mut self, disposition: Disposition) -> Self {
229 self.disposition = Some(disposition);
230 self
231 }
232
233 #[must_use]
239 pub fn with_context(mut self, context: EcologicalContext) -> Self {
240 self.context = Some(context);
241 self
242 }
243
244 pub fn build(self) -> Result<Entity, EntityBuildError> {
253 let species = self.species.ok_or(EntityBuildError::MissingSpecies)?;
255 let age = self.age.ok_or(EntityBuildError::MissingAge)?;
256
257 let id_string = self.id.unwrap_or_else(generate_uuid);
259 let id = match EntityId::new(id_string) {
260 Ok(id) => id,
261 Err(err) => return Err(EntityBuildError::InvalidId(err.reason)),
262 };
263
264 let birth_date = self.birth_date;
266
267 let life_stage = self
269 .life_stage
270 .unwrap_or_else(|| LifeStage::from_age_years_for_species(&species, age.as_years_f64()));
271
272 let hexaco = if let Some(h) = self.hexaco {
274 h
275 } else if let Some(profile) = self.personality {
276 Hexaco::from_profile(profile)
277 } else {
278 Hexaco::from_profile(PersonalityProfile::Balanced)
279 };
280
281 let person_characteristics = self.person_characteristics.unwrap_or_default();
283
284 let mut individual_state = IndividualState::new()
286 .with_hexaco(hexaco.clone())
287 .with_person_characteristics(person_characteristics);
288
289 if let Some(mood) = self.mood {
291 individual_state = individual_state.with_mood(mood);
292 } else {
293 individual_state = individual_state.with_mood(Mood::from_personality(&hexaco));
295 }
296 if let Some(needs) = self.needs {
297 individual_state = individual_state.with_needs(needs);
298 }
299 if let Some(mental_health) = self.mental_health {
300 individual_state = individual_state.with_mental_health(mental_health);
301 }
302 if let Some(social_cognition) = self.social_cognition {
303 individual_state = individual_state.with_social_cognition(social_cognition);
304 }
305 if let Some(disposition) = self.disposition {
306 individual_state = individual_state.with_disposition(disposition);
307 }
308
309 if let Some(context) = self.context {
311 Ok(Entity::new_with_context(
312 id,
313 species,
314 age,
315 birth_date,
316 life_stage,
317 individual_state,
318 context,
319 ))
320 } else {
321 Ok(Entity::new(
322 id,
323 species,
324 age,
325 birth_date,
326 life_stage,
327 individual_state,
328 ))
329 }
330 }
331}
332
333fn generate_uuid() -> String {
338 use std::sync::atomic::{AtomicU64, Ordering};
339 static COUNTER: AtomicU64 = AtomicU64::new(1);
340 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
341 format!("entity_{:016x}", count)
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn builder_sets_species() {
350 let entity = EntityBuilder::new()
351 .species(Species::Human)
352 .age(crate::types::Duration::years(30))
353 .build()
354 .unwrap();
355
356 assert_eq!(entity.species(), &Species::Human);
357 }
358
359 #[test]
360 fn builder_sets_life_stage() {
361 let entity = EntityBuilder::new()
362 .species(Species::Human)
363 .age(crate::types::Duration::years(30))
364 .life_stage(LifeStage::Adult)
365 .build()
366 .unwrap();
367
368 assert_eq!(entity.life_stage(), LifeStage::Adult);
369 }
370
371 #[test]
372 fn builder_sets_personality_profile() {
373 let entity = EntityBuilder::new()
374 .species(Species::Human)
375 .age(crate::types::Duration::years(30))
376 .personality(PersonalityProfile::Leader)
377 .build()
378 .unwrap();
379
380 assert!(entity.individual_state().hexaco().extraversion() > 0.5);
382 }
383
384 #[test]
385 fn builder_sets_person_characteristics() {
386 let pc = PersonCharacteristics::new().with_cognitive_ability_base(0.9);
387
388 let entity = EntityBuilder::new()
389 .species(Species::Human)
390 .age(crate::types::Duration::years(30))
391 .person_characteristics(pc)
392 .build()
393 .unwrap();
394
395 assert!(
396 (entity
397 .individual_state()
398 .person_characteristics()
399 .cognitive_ability()
400 .base()
401 - 0.9)
402 .abs()
403 < f32::EPSILON
404 );
405 }
406
407 #[test]
408 fn builder_produces_entity() {
409 let result = EntityBuilder::new().species(Species::Human).age(crate::types::Duration::years(30)).build();
410
411 assert!(result.is_ok());
412 let entity = result.unwrap();
413 assert_eq!(entity.species(), &Species::Human);
414 }
415
416 #[test]
417 fn builder_requires_species() {
418 let result = EntityBuilder::new().build();
419
420 assert_eq!(result.unwrap_err(), EntityBuildError::MissingSpecies);
421 }
422
423 #[test]
424 fn builder_defaults_life_stage() {
425 let entity = EntityBuilder::new()
427 .species(Species::Human)
428 .age(Duration::years(30))
429 .build()
430 .unwrap();
431
432 assert_eq!(entity.life_stage(), LifeStage::YoungAdult);
433
434 let adult = EntityBuilder::new()
436 .species(Species::Human)
437 .age(Duration::years(40))
438 .build()
439 .unwrap();
440
441 assert_eq!(adult.life_stage(), LifeStage::Adult);
442
443 let child = EntityBuilder::new()
445 .species(Species::Human)
446 .age(Duration::years(8))
447 .build()
448 .unwrap();
449
450 assert_eq!(child.life_stage(), LifeStage::Child);
451 }
452
453 #[test]
454 fn builder_sets_id() {
455 let entity = EntityBuilder::new()
456 .id("test_entity")
457 .species(Species::Human)
458 .age(crate::types::Duration::years(30))
459 .build()
460 .unwrap();
461
462 assert_eq!(entity.id().as_str(), "test_entity");
463 }
464
465 #[test]
466 fn builder_generates_id_if_not_set() {
467 let entity = EntityBuilder::new()
468 .species(Species::Human)
469 .age(crate::types::Duration::years(30))
470 .build()
471 .unwrap();
472
473 assert!(!entity.id().as_str().is_empty());
474 assert!(entity.id().as_str().starts_with("entity_"));
475 }
476
477 #[test]
478 fn builder_sets_age() {
479 let entity = EntityBuilder::new()
480 .species(Species::Human)
481 .age(Duration::years(25))
482 .build()
483 .unwrap();
484
485 assert_eq!(entity.age().as_years(), 25);
486 }
487
488 #[test]
489 fn builder_requires_age() {
490 let result = EntityBuilder::new().species(Species::Human).build();
491
492 assert_eq!(result.unwrap_err(), EntityBuildError::MissingAge);
493 }
494
495 #[test]
496 fn builder_sets_hexaco_directly() {
497 let hexaco = Hexaco::new().with_openness(0.9).with_neuroticism(-0.8);
498
499 let entity = EntityBuilder::new()
500 .species(Species::Human)
501 .age(crate::types::Duration::years(30))
502 .hexaco(hexaco)
503 .build()
504 .unwrap();
505
506 assert!((entity.individual_state().hexaco().openness() - 0.9).abs() < f32::EPSILON);
507 assert!((entity.individual_state().hexaco().neuroticism() - (-0.8)).abs() < f32::EPSILON);
508 }
509
510 #[test]
511 fn hexaco_overrides_personality() {
512 let hexaco = Hexaco::uniform(0.3);
513
514 let entity = EntityBuilder::new()
515 .species(Species::Human)
516 .age(crate::types::Duration::years(30))
517 .personality(PersonalityProfile::Leader) .hexaco(hexaco) .build()
520 .unwrap();
521
522 assert!((entity.individual_state().hexaco().extraversion() - 0.3).abs() < f32::EPSILON);
523 }
524
525 #[test]
526 fn personality_overrides_hexaco() {
527 let hexaco = Hexaco::uniform(0.3);
528
529 let entity = EntityBuilder::new()
530 .species(Species::Human)
531 .age(crate::types::Duration::years(30))
532 .hexaco(hexaco)
533 .personality(PersonalityProfile::Leader) .build()
535 .unwrap();
536
537 assert!(entity.individual_state().hexaco().extraversion() > 0.5);
539 }
540
541 #[test]
542 fn builder_clone() {
543 let builder = EntityBuilder::new()
544 .species(Species::Human)
545 .age(Duration::years(30));
546
547 let cloned = builder.clone();
548
549 let entity1 = builder.build().unwrap();
550 let entity2 = cloned.build().unwrap();
551
552 assert_eq!(entity1.species(), entity2.species());
553 assert_eq!(entity1.age(), entity2.age());
554 }
555
556 #[test]
557 fn empty_id_returns_error() {
558 let result = EntityBuilder::new().id("").species(Species::Human).age(crate::types::Duration::years(30)).build();
559
560 assert_eq!(
561 result.unwrap_err(),
562 EntityBuildError::InvalidId("ID cannot be empty".to_string())
563 );
564 }
565
566 #[test]
567 fn builder_debug() {
568 let builder = EntityBuilder::new().species(Species::Human);
569 let debug = format!("{:?}", builder);
570 assert!(debug.contains("EntityBuilder"));
571 }
572
573 #[test]
574 fn error_display() {
575 let err = EntityBuildError::MissingSpecies;
576 let display = format!("{}", err);
577 assert!(display.contains("Species"));
578
579 let err_age = EntityBuildError::MissingAge;
580 let display_age = format!("{}", err_age);
581 assert!(display_age.contains("Age"));
582
583 let err2 = EntityBuildError::InvalidId("test reason".to_string());
584 let display2 = format!("{}", err2);
585 assert!(display2.contains("test reason"));
586 }
587
588 #[test]
589 fn error_debug() {
590 let err = EntityBuildError::MissingSpecies;
591 let debug = format!("{:?}", err);
592 assert!(debug.contains("MissingSpecies"));
593
594 let err_age = EntityBuildError::MissingAge;
595 let debug_age = format!("{:?}", err_age);
596 assert!(debug_age.contains("MissingAge"));
597 }
598
599 #[test]
600 fn error_is_std_error() {
601 use std::error::Error;
602
603 let err: &dyn Error = &EntityBuildError::MissingSpecies;
604 assert!(err.source().is_none());
606
607 let err_age: &dyn Error = &EntityBuildError::MissingAge;
608 assert!(err_age.source().is_none());
609
610 let err2: &dyn Error = &EntityBuildError::InvalidId("test".to_string());
611 assert!(err2.source().is_none());
612 }
613
614 #[test]
615 fn builder_new() {
616 let builder = EntityBuilder::new();
617 let debug = format!("{:?}", builder);
618 assert!(debug.contains("EntityBuilder"));
619 }
620
621 #[test]
622 fn dog_age_derives_life_stage() {
623 let dog = EntityBuilder::new()
625 .species(Species::Dog)
626 .age(Duration::years(2))
627 .build()
628 .unwrap();
629
630 assert_eq!(dog.life_stage(), LifeStage::YoungAdult);
631 }
632
633 #[test]
634 fn explicit_life_stage_overrides_age_derived() {
635 let entity = EntityBuilder::new()
636 .species(Species::Human)
637 .age(Duration::years(30)) .life_stage(LifeStage::Elder) .build()
640 .unwrap();
641
642 assert_eq!(entity.life_stage(), LifeStage::Elder);
643 }
644
645 #[test]
646 fn default_personality_is_balanced() {
647 let entity = EntityBuilder::new()
648 .species(Species::Human)
649 .age(crate::types::Duration::years(30))
650 .build()
651 .unwrap();
652
653 assert!((entity.individual_state().hexaco().openness() - 0.0).abs() < 0.01);
655 }
656
657 #[test]
658 fn default_person_characteristics_are_neutral() {
659 let entity = EntityBuilder::new()
660 .species(Species::Human)
661 .age(crate::types::Duration::years(30))
662 .build()
663 .unwrap();
664
665 let pc = entity.individual_state().person_characteristics();
667 assert!(pc.resource() >= 0.3 && pc.resource() <= 0.7);
668 assert!(pc.force() >= 0.3 && pc.force() <= 0.7);
669 }
670
671 #[test]
672 fn builder_with_context() {
673 use crate::context::{Microsystem, WorkContext};
674 use crate::types::MicrosystemId;
675
676 let mut context = EcologicalContext::default();
677 let work_id = MicrosystemId::new("work_primary").unwrap();
678 context.add_microsystem(work_id, Microsystem::new_work(WorkContext::default()));
679
680 let entity = EntityBuilder::new()
681 .species(Species::Human)
682 .age(crate::types::Duration::years(30))
683 .with_context(context)
684 .build()
685 .unwrap();
686
687 assert_eq!(entity.context().microsystem_count(), 1);
688 }
689
690 #[test]
691 fn builder_without_context_uses_default() {
692 let entity = EntityBuilder::new()
693 .species(Species::Human)
694 .age(crate::types::Duration::years(30))
695 .build()
696 .unwrap();
697
698 assert_eq!(entity.context().microsystem_count(), 0);
700 }
701
702 #[test]
703 fn builder_sets_birth_date() {
704 let birth = Timestamp::from_ymd_hms(1990, 6, 15, 0, 0, 0);
705 let entity = EntityBuilder::new()
706 .id("person_001")
707 .species(Species::Human)
708 .age(crate::types::Duration::years(30))
709 .birth_date(birth)
710 .build()
711 .unwrap();
712
713 assert_eq!(entity.birth_date(), Some(birth));
714 }
715
716 #[test]
717 fn builder_without_birth_date_returns_none() {
718 let entity = EntityBuilder::new()
719 .species(Species::Human)
720 .age(crate::types::Duration::years(30))
721 .build()
722 .unwrap();
723
724 assert!(entity.birth_date().is_none());
725 }
726
727 #[test]
728 fn builder_birth_date_can_be_used_with_age() {
729 let birth = Timestamp::from_ymd_hms(1990, 6, 15, 0, 0, 0);
730 let entity = EntityBuilder::new()
731 .species(Species::Human)
732 .birth_date(birth)
733 .age(Duration::years(30))
734 .build()
735 .unwrap();
736
737 assert_eq!(entity.birth_date(), Some(birth));
739 assert_eq!(entity.age().as_years(), 30);
740 }
741
742 #[test]
743 fn builder_sets_mood() {
744 use crate::state::Mood;
745
746 let mood = Mood::new().with_valence_base(0.6).with_arousal_base(-0.3);
747
748 let entity = EntityBuilder::new()
749 .species(Species::Human)
750 .age(crate::types::Duration::years(30))
751 .mood(mood.clone())
752 .build()
753 .unwrap();
754
755 assert_eq!(entity.individual_state().mood(), &mood);
756 }
757
758 #[test]
759 fn builder_sets_needs() {
760 use crate::state::Needs;
761
762 let needs = Needs::new().with_fatigue_base(0.4).with_stress_base(0.2);
763
764 let entity = EntityBuilder::new()
765 .species(Species::Human)
766 .age(crate::types::Duration::years(30))
767 .needs(needs.clone())
768 .build()
769 .unwrap();
770
771 assert_eq!(entity.individual_state().needs(), &needs);
772 }
773
774 #[test]
775 fn builder_sets_mental_health() {
776 use crate::state::MentalHealth;
777
778 let mh = MentalHealth::new()
779 .with_depression_base(0.15)
780 .with_hopelessness_base(0.1);
781
782 let entity = EntityBuilder::new()
783 .species(Species::Human)
784 .age(crate::types::Duration::years(30))
785 .mental_health(mh.clone())
786 .build()
787 .unwrap();
788
789 assert_eq!(entity.individual_state().mental_health(), &mh);
790 }
791
792 #[test]
793 fn builder_sets_social_cognition() {
794 use crate::state::SocialCognition;
795
796 let sc = SocialCognition::new()
797 .with_loneliness_base(0.25)
798 .with_perceived_reciprocal_caring_base(0.7);
799
800 let entity = EntityBuilder::new()
801 .species(Species::Human)
802 .age(crate::types::Duration::years(30))
803 .social_cognition(sc.clone())
804 .build()
805 .unwrap();
806
807 assert_eq!(entity.individual_state().social_cognition(), &sc);
808 }
809
810 #[test]
811 fn builder_sets_disposition() {
812 use crate::state::Disposition;
813
814 let disp = Disposition::new()
815 .with_empathy_base(0.8)
816 .with_impulse_control_base(0.7);
817
818 let entity = EntityBuilder::new()
819 .species(Species::Human)
820 .age(crate::types::Duration::years(30))
821 .disposition(disp.clone())
822 .build()
823 .unwrap();
824
825 assert_eq!(entity.individual_state().disposition(), &disp);
826 }
827
828 #[test]
829 fn builder_without_optional_state_uses_defaults() {
830 use crate::state::{Disposition, MentalHealth, Needs, SocialCognition};
831
832 let entity = EntityBuilder::new()
833 .species(Species::Human)
834 .age(crate::types::Duration::years(30))
835 .build()
836 .unwrap();
837
838 assert_eq!(entity.individual_state().needs(), &Needs::default());
841 assert_eq!(
842 entity.individual_state().mental_health(),
843 &MentalHealth::default()
844 );
845 assert_eq!(
846 entity.individual_state().social_cognition(),
847 &SocialCognition::default()
848 );
849 assert_eq!(
850 entity.individual_state().disposition(),
851 &Disposition::default()
852 );
853 }
854
855 #[test]
856 fn builder_derives_mood_from_personality() {
857 let entity = EntityBuilder::new()
859 .species(Species::Human)
860 .age(crate::types::Duration::years(30))
861 .personality(PersonalityProfile::Leader) .build()
863 .unwrap();
864
865 let valence = entity.individual_state().mood().valence_base();
867 assert!(valence > 0.0);
868 }
869
870 #[test]
871 fn explicit_mood_overrides_personality_derived() {
872 use crate::state::Mood;
873
874 let explicit_mood = Mood::new()
876 .with_valence_base(-0.5) .with_arousal_base(0.3);
878
879 let entity = EntityBuilder::new()
880 .species(Species::Human)
881 .age(crate::types::Duration::years(30))
882 .personality(PersonalityProfile::Leader) .mood(explicit_mood.clone()) .build()
885 .unwrap();
886
887 assert_eq!(entity.individual_state().mood(), &explicit_mood);
889 assert!((entity.individual_state().mood().valence_base() - (-0.5)).abs() < f32::EPSILON);
890 }
891
892 #[test]
893 fn builder_sets_all_state_components_together() {
894 use crate::state::{Disposition, MentalHealth, Mood, Needs, SocialCognition};
895
896 let mood = Mood::new().with_valence_base(0.5);
897 let needs = Needs::new().with_purpose_base(0.8);
898 let mh = MentalHealth::new().with_depression_base(0.1);
899 let sc = SocialCognition::new().with_loneliness_base(0.2);
900 let disp = Disposition::new().with_empathy_base(0.9);
901
902 let entity = EntityBuilder::new()
903 .species(Species::Human)
904 .age(crate::types::Duration::years(30))
905 .mood(mood.clone())
906 .needs(needs.clone())
907 .mental_health(mh.clone())
908 .social_cognition(sc.clone())
909 .disposition(disp.clone())
910 .build()
911 .unwrap();
912
913 assert_eq!(entity.individual_state().mood(), &mood);
914 assert_eq!(entity.individual_state().needs(), &needs);
915 assert_eq!(entity.individual_state().mental_health(), &mh);
916 assert_eq!(entity.individual_state().social_cognition(), &sc);
917 assert_eq!(entity.individual_state().disposition(), &disp);
918 }
919}