use crate::models::{Operator, OperatorExt, OperatorRank};
use crate::traits::Phase;
use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElectionPolicyConfig {
pub default_policy: LeadershipPolicy,
pub min_leader_rank: Option<OperatorRank>,
pub allow_autonomous_leaders: bool,
pub max_cognitive_load: f32,
pub max_fatigue: f32,
}
impl Default for ElectionPolicyConfig {
fn default() -> Self {
Self {
default_policy: LeadershipPolicy::Hybrid {
authority_weight: 0.6,
technical_weight: 0.4,
},
min_leader_rank: Some(OperatorRank::E5), allow_autonomous_leaders: false, max_cognitive_load: 0.85,
max_fatigue: 0.75,
}
}
}
impl ElectionPolicyConfig {
pub fn load_from_env() -> Self {
let mut config = Self::default();
if let Ok(policy_str) = env::var("CAP_ELECTION_POLICY") {
config.default_policy = match policy_str.to_lowercase().as_str() {
"rank_dominant" => LeadershipPolicy::RankDominant,
"technical_dominant" => LeadershipPolicy::TechnicalDominant,
"hybrid" => {
let authority_weight = env::var("CAP_AUTHORITY_WEIGHT")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(0.6);
LeadershipPolicy::Hybrid {
authority_weight,
technical_weight: 1.0 - authority_weight,
}
}
"contextual" => LeadershipPolicy::Contextual,
_ => config.default_policy, };
}
if let Ok(allow_str) = env::var("CAP_ALLOW_AUTONOMOUS_LEADERS") {
config.allow_autonomous_leaders = allow_str.to_lowercase() == "true";
}
if let Ok(rank_str) = env::var("CAP_MIN_LEADER_RANK") {
config.min_leader_rank = parse_rank_string(&rank_str);
}
if let Ok(load_str) = env::var("CAP_MAX_COGNITIVE_LOAD") {
if let Ok(load) = load_str.parse::<f32>() {
config.max_cognitive_load = load.clamp(0.0, 1.0);
}
}
if let Ok(fatigue_str) = env::var("CAP_MAX_FATIGUE") {
if let Ok(fatigue) = fatigue_str.parse::<f32>() {
config.max_fatigue = fatigue.clamp(0.0, 1.0);
}
}
config
}
pub fn is_qualified_leader(&self, operator: &Operator) -> bool {
if operator.cognitive_load() > self.max_cognitive_load {
return false;
}
if operator.fatigue() > self.max_fatigue {
return false;
}
if let Some(min_rank) = &self.min_leader_rank {
let op_rank = OperatorRank::try_from(operator.rank).ok();
if let Some(op_rank) = op_rank {
if op_rank < *min_rank {
return false;
}
} else {
return false; }
}
true
}
pub fn allows_autonomous_leader(&self) -> bool {
self.allow_autonomous_leaders
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LeadershipPolicy {
RankDominant,
TechnicalDominant,
Hybrid {
authority_weight: f64,
technical_weight: f64,
},
Contextual,
}
impl LeadershipPolicy {
pub fn get_weights(&self, context: &ElectionContext) -> (f64, f64) {
match self {
Self::RankDominant => (1.0, 0.0),
Self::TechnicalDominant => (0.0, 1.0),
Self::Hybrid {
authority_weight,
technical_weight,
} => (*authority_weight, *technical_weight),
Self::Contextual => Self::compute_contextual_weights(context),
}
}
fn compute_contextual_weights(context: &ElectionContext) -> (f64, f64) {
match context.mission_phase {
Phase::Discovery => (0.7, 0.3), Phase::Cell => (0.6, 0.4), Phase::Hierarchy => (0.8, 0.2), Phase::Unspecified => (0.6, 0.4), }
}
}
#[derive(Debug, Clone)]
pub struct ElectionContext {
pub policy: LeadershipPolicy,
pub mission_phase: Phase,
pub authority_required: bool,
pub casualty_count: usize,
}
impl Default for ElectionContext {
fn default() -> Self {
Self {
policy: LeadershipPolicy::Hybrid {
authority_weight: 0.6,
technical_weight: 0.4,
},
mission_phase: Phase::Cell,
authority_required: false,
casualty_count: 0,
}
}
}
impl ElectionContext {
pub fn new(policy: LeadershipPolicy, mission_phase: Phase) -> Self {
Self {
policy,
mission_phase,
authority_required: false,
casualty_count: 0,
}
}
pub fn with_authority_required(mut self, required: bool) -> Self {
self.authority_required = required;
self
}
pub fn with_casualties(mut self, count: usize) -> Self {
self.casualty_count = count;
self
}
}
fn parse_rank_string(s: &str) -> Option<OperatorRank> {
let s = s.trim().to_uppercase();
if let Some(stripped) = s.strip_prefix('E') {
match stripped {
"1" => Some(OperatorRank::E1),
"2" => Some(OperatorRank::E2),
"3" => Some(OperatorRank::E3),
"4" => Some(OperatorRank::E4),
"5" => Some(OperatorRank::E5),
"6" => Some(OperatorRank::E6),
"7" => Some(OperatorRank::E7),
"8" => Some(OperatorRank::E8),
"9" => Some(OperatorRank::E9),
_ => None,
}
} else if let Some(stripped) = s.strip_prefix('W') {
match stripped {
"1" => Some(OperatorRank::W1),
"2" => Some(OperatorRank::W2),
"3" => Some(OperatorRank::W3),
"4" => Some(OperatorRank::W4),
"5" => Some(OperatorRank::W5),
_ => None,
}
} else if let Some(stripped) = s.strip_prefix('O') {
match stripped {
"1" => Some(OperatorRank::O1),
"2" => Some(OperatorRank::O2),
"3" => Some(OperatorRank::O3),
"4" => Some(OperatorRank::O4),
"5" => Some(OperatorRank::O5),
"6" => Some(OperatorRank::O6),
"7" => Some(OperatorRank::O7),
"8" => Some(OperatorRank::O8),
"9" => Some(OperatorRank::O9),
"10" => Some(OperatorRank::O10),
_ => None,
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{AuthorityLevel, OperatorExt};
#[test]
fn test_default_config() {
let config = ElectionPolicyConfig::default();
assert!(matches!(
config.default_policy,
LeadershipPolicy::Hybrid { .. }
));
assert_eq!(config.min_leader_rank, Some(OperatorRank::E5));
assert!(!config.allow_autonomous_leaders);
assert_eq!(config.max_cognitive_load, 0.85);
assert_eq!(config.max_fatigue, 0.75);
}
#[test]
fn test_load_from_env() {
env::set_var("CAP_ELECTION_POLICY", "rank_dominant");
env::set_var("CAP_ALLOW_AUTONOMOUS_LEADERS", "true");
env::set_var("CAP_MIN_LEADER_RANK", "E7");
let config = ElectionPolicyConfig::load_from_env();
assert_eq!(config.default_policy, LeadershipPolicy::RankDominant);
assert!(config.allow_autonomous_leaders);
assert_eq!(config.min_leader_rank, Some(OperatorRank::E7));
env::remove_var("CAP_ELECTION_POLICY");
env::remove_var("CAP_ALLOW_AUTONOMOUS_LEADERS");
env::remove_var("CAP_MIN_LEADER_RANK");
}
#[test]
fn test_is_qualified_leader() {
let config = ElectionPolicyConfig::default();
let qualified = Operator::new(
"op_1".to_string(),
"SFC Smith".to_string(),
OperatorRank::E7,
AuthorityLevel::Commander,
"11B".to_string(),
);
assert!(config.is_qualified_leader(&qualified));
let low_rank = Operator::new(
"op_2".to_string(),
"SPC Jones".to_string(),
OperatorRank::E4,
AuthorityLevel::Commander,
"11B".to_string(),
);
assert!(!config.is_qualified_leader(&low_rank));
let mut overloaded = qualified.clone();
overloaded.update_cognitive_load(0.95);
assert!(!config.is_qualified_leader(&overloaded));
let mut fatigued = qualified.clone();
fatigued.update_fatigue(0.90);
assert!(!config.is_qualified_leader(&fatigued));
}
#[test]
fn test_leadership_policy_weights() {
let context = ElectionContext::default();
let policy = LeadershipPolicy::RankDominant;
assert_eq!(policy.get_weights(&context), (1.0, 0.0));
let policy = LeadershipPolicy::TechnicalDominant;
assert_eq!(policy.get_weights(&context), (0.0, 1.0));
let policy = LeadershipPolicy::Hybrid {
authority_weight: 0.7,
technical_weight: 0.3,
};
assert_eq!(policy.get_weights(&context), (0.7, 0.3));
}
#[test]
fn test_contextual_policy_weights() {
let policy = LeadershipPolicy::Contextual;
let context = ElectionContext::new(policy.clone(), Phase::Discovery);
let (auth, _tech) = policy.get_weights(&context);
assert_eq!(auth, 0.7);
let context = ElectionContext::new(policy.clone(), Phase::Cell);
let (auth, _tech) = policy.get_weights(&context);
assert_eq!(auth, 0.6);
let context = ElectionContext::new(policy.clone(), Phase::Hierarchy);
let (auth, _tech) = policy.get_weights(&context);
assert_eq!(auth, 0.8);
}
#[test]
fn test_contextual_policy_with_authority_required() {
let policy = LeadershipPolicy::Contextual;
let context =
ElectionContext::new(policy.clone(), Phase::Cell).with_authority_required(true);
let (auth, _tech) = policy.get_weights(&context);
assert_eq!(auth, 0.6); }
#[test]
fn test_parse_rank_string() {
assert_eq!(parse_rank_string("E5"), Some(OperatorRank::E5));
assert_eq!(parse_rank_string("e7"), Some(OperatorRank::E7));
assert_eq!(parse_rank_string("W3"), Some(OperatorRank::W3));
assert_eq!(parse_rank_string("O3"), Some(OperatorRank::O3));
assert_eq!(parse_rank_string("O10"), Some(OperatorRank::O10));
assert_eq!(parse_rank_string("invalid"), None);
assert_eq!(parse_rank_string("E99"), None);
}
#[test]
fn test_election_context_builder() {
let context = ElectionContext::new(
LeadershipPolicy::Hybrid {
authority_weight: 0.6,
technical_weight: 0.4,
},
Phase::Hierarchy,
)
.with_authority_required(true)
.with_casualties(2);
assert_eq!(context.mission_phase, Phase::Hierarchy);
assert!(context.authority_required);
assert_eq!(context.casualty_count, 2);
}
}