use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EqProfile {
pub perception: f32,
pub facilitation: f32,
pub understanding: f32,
pub management: f32,
}
impl Default for EqProfile {
fn default() -> Self {
Self {
perception: 0.5,
facilitation: 0.5,
understanding: 0.5,
management: 0.5,
}
}
}
impl EqProfile {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_scores(
perception: f32,
facilitation: f32,
understanding: f32,
management: f32,
) -> Self {
Self {
perception: perception.clamp(0.0, 1.0),
facilitation: facilitation.clamp(0.0, 1.0),
understanding: understanding.clamp(0.0, 1.0),
management: management.clamp(0.0, 1.0),
}
}
#[must_use]
#[inline]
pub fn overall(&self) -> f32 {
self.perception * 0.15
+ self.facilitation * 0.20
+ self.understanding * 0.30
+ self.management * 0.35
}
#[must_use]
#[inline]
pub fn get(&self, branch: EqBranch) -> f32 {
match branch {
EqBranch::Perception => self.perception,
EqBranch::Facilitation => self.facilitation,
EqBranch::Understanding => self.understanding,
EqBranch::Management => self.management,
}
}
#[inline]
pub fn set(&mut self, branch: EqBranch, value: f32) {
let v = value.clamp(0.0, 1.0);
match branch {
EqBranch::Perception => self.perception = v,
EqBranch::Facilitation => self.facilitation = v,
EqBranch::Understanding => self.understanding = v,
EqBranch::Management => self.management = v,
}
}
#[must_use]
pub fn level(&self) -> EqLevel {
let o = self.overall();
if o >= 0.8 {
EqLevel::Exceptional
} else if o >= 0.6 {
EqLevel::High
} else if o >= 0.4 {
EqLevel::Average
} else if o >= 0.2 {
EqLevel::Low
} else {
EqLevel::Minimal
}
}
#[must_use]
pub fn perception_bonus(&self) -> f32 {
0.5 + self.perception
}
#[must_use]
pub fn facilitation_bonus(&self) -> f32 {
0.5 + self.facilitation
}
#[must_use]
pub fn management_bonus(&self) -> f32 {
0.5 + self.management
}
#[must_use]
pub fn stress_recovery_bonus(&self) -> f32 {
0.8 + self.management * 0.7
}
#[must_use]
pub fn contagion_resistance(&self) -> f32 {
self.management * 0.5
}
#[must_use]
pub fn appraisal_bonus(&self) -> f32 {
0.5 + self.understanding
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum EqBranch {
Perception,
Facilitation,
Understanding,
Management,
}
impl EqBranch {
pub const ALL: &'static [EqBranch] = &[
Self::Perception,
Self::Facilitation,
Self::Understanding,
Self::Management,
];
}
impl fmt::Display for EqBranch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Perception => "perception",
Self::Facilitation => "facilitation",
Self::Understanding => "understanding",
Self::Management => "management",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum EqLevel {
Minimal,
Low,
Average,
High,
Exceptional,
}
impl fmt::Display for EqLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Minimal => "minimal",
Self::Low => "low",
Self::Average => "average",
Self::High => "high",
Self::Exceptional => "exceptional",
};
f.write_str(s)
}
}
#[cfg(feature = "traits")]
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn eq_from_personality(profile: &crate::traits::PersonalityProfile) -> EqProfile {
use crate::traits::TraitKind;
let empathy = profile.get_trait(TraitKind::Empathy).normalized();
let curiosity = profile.get_trait(TraitKind::Curiosity).normalized();
let creativity = profile.get_trait(TraitKind::Creativity).normalized();
let confidence = profile.get_trait(TraitKind::Confidence).normalized();
let patience = profile.get_trait(TraitKind::Patience).normalized();
let pedagogy = profile.get_trait(TraitKind::Pedagogy).normalized();
let formality = profile.get_trait(TraitKind::Formality).normalized();
let to_score = |v: f32| ((v + 1.0) / 2.0).clamp(0.0, 1.0);
EqProfile {
perception: to_score((empathy + curiosity) / 2.0),
facilitation: to_score((creativity + confidence) / 2.0),
understanding: to_score((empathy + patience + pedagogy) / 3.0),
management: to_score((patience + confidence + formality) / 3.0),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn compose_eq_prompt(eq: &EqProfile) -> String {
use std::fmt::Write;
let mut prompt = String::with_capacity(200);
prompt.push_str("## Emotional Intelligence\n\n");
let _ = writeln!(
prompt,
"- Overall EQ: {} ({:.0}%)",
eq.level(),
eq.overall() * 100.0
);
for &branch in EqBranch::ALL {
let score = eq.get(branch);
let label = match score {
s if s >= 0.8 => "exceptional",
s if s >= 0.6 => "strong",
s if s >= 0.4 => "moderate",
s if s >= 0.2 => "developing",
_ => "limited",
};
let _ = writeln!(prompt, "- {branch}: {label}");
}
prompt
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let eq = EqProfile::new();
assert!((eq.perception - 0.5).abs() < f32::EPSILON);
assert!((eq.facilitation - 0.5).abs() < f32::EPSILON);
assert!((eq.understanding - 0.5).abs() < f32::EPSILON);
assert!((eq.management - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_with_scores_clamps() {
let eq = EqProfile::with_scores(1.5, -0.5, 0.7, 0.3);
assert!((eq.perception - 1.0).abs() < f32::EPSILON);
assert!(eq.facilitation.abs() < f32::EPSILON);
assert!((eq.understanding - 0.7).abs() < f32::EPSILON);
}
#[test]
fn test_overall_weighted() {
let eq = EqProfile::with_scores(1.0, 1.0, 1.0, 1.0);
assert!((eq.overall() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_overall_zero() {
let eq = EqProfile::with_scores(0.0, 0.0, 0.0, 0.0);
assert!(eq.overall().abs() < f32::EPSILON);
}
#[test]
fn test_overall_management_heavy() {
let high_mgmt = EqProfile::with_scores(0.0, 0.0, 0.0, 1.0);
let high_perc = EqProfile::with_scores(1.0, 0.0, 0.0, 0.0);
assert!(high_mgmt.overall() > high_perc.overall());
}
#[test]
fn test_get_set() {
let mut eq = EqProfile::new();
eq.set(EqBranch::Perception, 0.9);
assert!((eq.get(EqBranch::Perception) - 0.9).abs() < f32::EPSILON);
eq.set(EqBranch::Management, 1.5); assert!((eq.get(EqBranch::Management) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_level_exceptional() {
let eq = EqProfile::with_scores(1.0, 1.0, 1.0, 1.0);
assert_eq!(eq.level(), EqLevel::Exceptional);
}
#[test]
fn test_level_minimal() {
let eq = EqProfile::with_scores(0.0, 0.0, 0.0, 0.0);
assert_eq!(eq.level(), EqLevel::Minimal);
}
#[test]
fn test_level_average() {
let eq = EqProfile::new(); assert_eq!(eq.level(), EqLevel::Average);
}
#[test]
fn test_perception_bonus_range() {
let low = EqProfile::with_scores(0.0, 0.5, 0.5, 0.5);
let high = EqProfile::with_scores(1.0, 0.5, 0.5, 0.5);
assert!((low.perception_bonus() - 0.5).abs() < f32::EPSILON);
assert!((high.perception_bonus() - 1.5).abs() < f32::EPSILON);
}
#[test]
fn test_management_bonus_range() {
let low = EqProfile::with_scores(0.5, 0.5, 0.5, 0.0);
let high = EqProfile::with_scores(0.5, 0.5, 0.5, 1.0);
assert!((low.management_bonus() - 0.5).abs() < f32::EPSILON);
assert!((high.management_bonus() - 1.5).abs() < f32::EPSILON);
}
#[test]
fn test_stress_recovery_bonus() {
let eq = EqProfile::with_scores(0.5, 0.5, 0.5, 1.0);
assert!((eq.stress_recovery_bonus() - 1.5).abs() < f32::EPSILON);
let low = EqProfile::with_scores(0.5, 0.5, 0.5, 0.0);
assert!((low.stress_recovery_bonus() - 0.8).abs() < f32::EPSILON);
}
#[test]
fn test_contagion_resistance() {
let eq = EqProfile::with_scores(0.5, 0.5, 0.5, 1.0);
assert!((eq.contagion_resistance() - 0.5).abs() < f32::EPSILON);
let low = EqProfile::with_scores(0.5, 0.5, 0.5, 0.0);
assert!(low.contagion_resistance().abs() < f32::EPSILON);
}
#[test]
fn test_branch_display() {
assert_eq!(EqBranch::Perception.to_string(), "perception");
assert_eq!(EqBranch::Management.to_string(), "management");
}
#[test]
fn test_level_display() {
assert_eq!(EqLevel::Exceptional.to_string(), "exceptional");
assert_eq!(EqLevel::Minimal.to_string(), "minimal");
}
#[test]
fn test_branch_all() {
assert_eq!(EqBranch::ALL.len(), 4);
}
#[test]
fn test_compose_prompt() {
let eq = EqProfile::with_scores(0.9, 0.7, 0.8, 0.6);
let prompt = compose_eq_prompt(&eq);
assert!(prompt.contains("## Emotional Intelligence"));
assert!(prompt.contains("perception"));
assert!(prompt.contains("management"));
}
#[test]
fn test_serde_profile() {
let eq = EqProfile::with_scores(0.8, 0.6, 0.7, 0.9);
let json = serde_json::to_string(&eq).unwrap();
let eq2: EqProfile = serde_json::from_str(&json).unwrap();
assert!((eq2.perception - eq.perception).abs() < f32::EPSILON);
assert!((eq2.management - eq.management).abs() < f32::EPSILON);
}
#[test]
fn test_serde_branch() {
let b = EqBranch::Understanding;
let json = serde_json::to_string(&b).unwrap();
let b2: EqBranch = serde_json::from_str(&json).unwrap();
assert_eq!(b2, b);
}
#[test]
fn test_serde_level() {
let l = EqLevel::High;
let json = serde_json::to_string(&l).unwrap();
let l2: EqLevel = serde_json::from_str(&json).unwrap();
assert_eq!(l2, l);
}
#[cfg(feature = "traits")]
#[test]
fn test_eq_from_personality_empathetic() {
let mut p = crate::traits::PersonalityProfile::new("empath");
p.set_trait(
crate::traits::TraitKind::Empathy,
crate::traits::TraitLevel::Highest,
);
p.set_trait(
crate::traits::TraitKind::Patience,
crate::traits::TraitLevel::Highest,
);
p.set_trait(
crate::traits::TraitKind::Curiosity,
crate::traits::TraitLevel::High,
);
let eq = eq_from_personality(&p);
assert!(eq.perception > 0.6, "perception: {}", eq.perception);
assert!(
eq.understanding > 0.6,
"understanding: {}",
eq.understanding
);
}
#[cfg(feature = "traits")]
#[test]
fn test_eq_from_personality_stoic() {
let mut p = crate::traits::PersonalityProfile::new("stoic");
p.set_trait(
crate::traits::TraitKind::Formality,
crate::traits::TraitLevel::Highest,
);
p.set_trait(
crate::traits::TraitKind::Confidence,
crate::traits::TraitLevel::Highest,
);
p.set_trait(
crate::traits::TraitKind::Empathy,
crate::traits::TraitLevel::Lowest,
);
let eq = eq_from_personality(&p);
assert!(
eq.management > eq.perception,
"mgmt={} perc={}",
eq.management,
eq.perception
);
}
#[cfg(feature = "traits")]
#[test]
fn test_eq_from_personality_balanced() {
let p = crate::traits::PersonalityProfile::new("balanced");
let eq = eq_from_personality(&p);
for &branch in EqBranch::ALL {
let s = eq.get(branch);
assert!((s - 0.5).abs() < 0.1, "{branch}: {s} (expected ~0.5)");
}
}
}