use super::{HierarchyStrategy, NodeRole};
use crate::beacon::{GeographicBeacon, HierarchyLevel, NodeMobility, NodeProfile};
#[derive(Debug, Clone)]
pub struct ElectionWeights {
pub mobility: f64,
pub resources: f64,
pub battery: f64,
}
impl Default for ElectionWeights {
fn default() -> Self {
Self {
mobility: 0.4,
resources: 0.4,
battery: 0.2,
}
}
}
#[derive(Debug, Clone)]
pub struct ElectionConfig {
pub priority_weights: ElectionWeights,
pub hysteresis: f64,
}
impl Default for ElectionConfig {
fn default() -> Self {
Self {
priority_weights: ElectionWeights::default(),
hysteresis: 0.1, }
}
}
#[derive(Debug, Clone)]
pub struct DynamicHierarchyStrategy {
pub base_level: HierarchyLevel,
pub election_config: ElectionConfig,
pub allow_level_transitions: bool,
}
impl DynamicHierarchyStrategy {
pub fn new(
base_level: HierarchyLevel,
election_config: ElectionConfig,
allow_level_transitions: bool,
) -> Self {
Self {
base_level,
election_config,
allow_level_transitions,
}
}
fn calculate_leadership_score(&self, profile: &NodeProfile) -> f64 {
let weights = &self.election_config.priority_weights;
let mut score = 0.0;
let mobility_score = match profile.mobility {
NodeMobility::Static => 1.0,
NodeMobility::SemiMobile => 0.6,
NodeMobility::Mobile => 0.3,
};
score += mobility_score * weights.mobility;
let cpu_score = 1.0 - (profile.resources.cpu_usage_percent as f64 / 100.0);
let mem_score = 1.0 - (profile.resources.memory_usage_percent as f64 / 100.0);
let resource_score = (cpu_score + mem_score) / 2.0;
score += resource_score * weights.resources;
let battery_score = profile
.resources
.battery_percent
.map(|b| b as f64 / 100.0)
.unwrap_or(1.0);
score += battery_score * weights.battery;
if profile.can_parent {
score *= 1.1;
}
score *= 1.0 + (profile.parent_priority as f64 / 255.0);
score
}
fn calculate_leadership_score_from_beacon(&self, beacon: &GeographicBeacon) -> f64 {
let weights = &self.election_config.priority_weights;
let mut score = 0.0;
if let Some(mobility) = beacon.mobility {
let mobility_score = match mobility {
NodeMobility::Static => 1.0,
NodeMobility::SemiMobile => 0.6,
NodeMobility::Mobile => 0.3,
};
score += mobility_score * weights.mobility;
} else {
score += 0.5 * weights.mobility; }
if let Some(ref resources) = beacon.resources {
let cpu_score = 1.0 - (resources.cpu_usage_percent as f64 / 100.0);
let mem_score = 1.0 - (resources.memory_usage_percent as f64 / 100.0);
let resource_score = (cpu_score + mem_score) / 2.0;
score += resource_score * weights.resources;
let battery_score = resources
.battery_percent
.map(|b| b as f64 / 100.0)
.unwrap_or(1.0);
score += battery_score * weights.battery;
} else {
score += 0.5 * (weights.resources + weights.battery); }
if beacon.can_parent {
score *= 1.1;
}
score *= 1.0 + (beacon.parent_priority as f64 / 255.0);
score
}
}
impl HierarchyStrategy for DynamicHierarchyStrategy {
fn determine_level(&self, _node_profile: &NodeProfile) -> HierarchyLevel {
self.base_level
}
fn determine_role(
&self,
node_profile: &NodeProfile,
nearby_peers: &[GeographicBeacon],
) -> NodeRole {
let same_level_peers: Vec<&GeographicBeacon> = nearby_peers
.iter()
.filter(|b| b.hierarchy_level == self.base_level)
.collect();
if same_level_peers.is_empty() {
return NodeRole::Standalone;
}
let my_score = self.calculate_leadership_score(node_profile);
let best_peer_score = same_level_peers
.iter()
.map(|p| self.calculate_leadership_score_from_beacon(p))
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(0.0);
let threshold = best_peer_score * (1.0 + self.election_config.hysteresis);
if my_score >= threshold {
NodeRole::Leader
} else {
NodeRole::Member
}
}
fn can_transition(&self, _current_level: HierarchyLevel, _new_level: HierarchyLevel) -> bool {
self.allow_level_transitions
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::beacon::{GeoPosition, NodeResources};
fn create_high_capability_profile() -> NodeProfile {
NodeProfile {
mobility: NodeMobility::Static,
resources: NodeResources {
cpu_cores: 8,
memory_mb: 8192,
bandwidth_mbps: 1000,
cpu_usage_percent: 20,
memory_usage_percent: 30,
battery_percent: None, },
can_parent: true,
prefer_leaf: false,
parent_priority: 200,
}
}
fn create_low_capability_profile() -> NodeProfile {
NodeProfile {
mobility: NodeMobility::Mobile,
resources: NodeResources {
cpu_cores: 2,
memory_mb: 1024,
bandwidth_mbps: 50,
cpu_usage_percent: 70,
memory_usage_percent: 80,
battery_percent: Some(30),
},
can_parent: false,
prefer_leaf: true,
parent_priority: 50,
}
}
#[test]
fn test_leadership_score_prefers_high_capability() {
let strategy =
DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
let high_cap = create_high_capability_profile();
let low_cap = create_low_capability_profile();
let high_score = strategy.calculate_leadership_score(&high_cap);
let low_score = strategy.calculate_leadership_score(&low_cap);
assert!(high_score > low_score);
}
#[test]
fn test_role_determination_standalone_when_no_peers() {
let strategy =
DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
let profile = create_high_capability_profile();
let role = strategy.determine_role(&profile, &[]);
assert_eq!(role, NodeRole::Standalone);
}
#[test]
fn test_role_determination_leader_with_high_capability() {
let strategy =
DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
let high_cap = create_high_capability_profile();
let mut low_cap_beacon = GeographicBeacon::new(
"peer1".to_string(),
GeoPosition::new(37.7750, -122.4195),
HierarchyLevel::Squad,
);
low_cap_beacon.mobility = Some(NodeMobility::Mobile);
low_cap_beacon.resources = Some(NodeResources {
cpu_cores: 2,
memory_mb: 1024,
bandwidth_mbps: 50,
cpu_usage_percent: 70,
memory_usage_percent: 80,
battery_percent: Some(30),
});
low_cap_beacon.can_parent = false;
low_cap_beacon.parent_priority = 50;
let role = strategy.determine_role(&high_cap, &[low_cap_beacon]);
assert_eq!(role, NodeRole::Leader);
}
#[test]
fn test_level_transitions_allowed_when_configured() {
let strategy = DynamicHierarchyStrategy::new(
HierarchyLevel::Squad,
ElectionConfig::default(),
true, );
assert!(strategy.can_transition(HierarchyLevel::Squad, HierarchyLevel::Platoon));
}
#[test]
fn test_level_transitions_disabled_when_configured() {
let strategy = DynamicHierarchyStrategy::new(
HierarchyLevel::Squad,
ElectionConfig::default(),
false, );
assert!(!strategy.can_transition(HierarchyLevel::Squad, HierarchyLevel::Platoon));
}
}