1use super::{HierarchyStrategy, NodeRole};
8use crate::beacon::{GeographicBeacon, HierarchyLevel, NodeMobility, NodeProfile};
9
10#[derive(Debug, Clone)]
12pub struct ElectionWeights {
13 pub mobility: f64,
15
16 pub resources: f64,
18
19 pub battery: f64,
21}
22
23impl Default for ElectionWeights {
24 fn default() -> Self {
25 Self {
26 mobility: 0.4,
27 resources: 0.4,
28 battery: 0.2,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct ElectionConfig {
36 pub priority_weights: ElectionWeights,
38
39 pub hysteresis: f64,
42}
43
44impl Default for ElectionConfig {
45 fn default() -> Self {
46 Self {
47 priority_weights: ElectionWeights::default(),
48 hysteresis: 0.1, }
50 }
51}
52
53#[derive(Debug, Clone)]
75pub struct DynamicHierarchyStrategy {
76 pub base_level: HierarchyLevel,
78
79 pub election_config: ElectionConfig,
81
82 pub allow_level_transitions: bool,
84}
85
86impl DynamicHierarchyStrategy {
87 pub fn new(
89 base_level: HierarchyLevel,
90 election_config: ElectionConfig,
91 allow_level_transitions: bool,
92 ) -> Self {
93 Self {
94 base_level,
95 election_config,
96 allow_level_transitions,
97 }
98 }
99
100 fn calculate_leadership_score(&self, profile: &NodeProfile) -> f64 {
104 let weights = &self.election_config.priority_weights;
105 let mut score = 0.0;
106
107 let mobility_score = match profile.mobility {
109 NodeMobility::Static => 1.0,
110 NodeMobility::SemiMobile => 0.6,
111 NodeMobility::Mobile => 0.3,
112 };
113 score += mobility_score * weights.mobility;
114
115 let cpu_score = 1.0 - (profile.resources.cpu_usage_percent as f64 / 100.0);
117 let mem_score = 1.0 - (profile.resources.memory_usage_percent as f64 / 100.0);
118 let resource_score = (cpu_score + mem_score) / 2.0;
119 score += resource_score * weights.resources;
120
121 let battery_score = profile
123 .resources
124 .battery_percent
125 .map(|b| b as f64 / 100.0)
126 .unwrap_or(1.0);
127 score += battery_score * weights.battery;
128
129 if profile.can_parent {
131 score *= 1.1;
132 }
133
134 score *= 1.0 + (profile.parent_priority as f64 / 255.0);
136
137 score
138 }
139
140 fn calculate_leadership_score_from_beacon(&self, beacon: &GeographicBeacon) -> f64 {
142 let weights = &self.election_config.priority_weights;
143 let mut score = 0.0;
144
145 if let Some(mobility) = beacon.mobility {
147 let mobility_score = match mobility {
148 NodeMobility::Static => 1.0,
149 NodeMobility::SemiMobile => 0.6,
150 NodeMobility::Mobile => 0.3,
151 };
152 score += mobility_score * weights.mobility;
153 } else {
154 score += 0.5 * weights.mobility; }
156
157 if let Some(ref resources) = beacon.resources {
159 let cpu_score = 1.0 - (resources.cpu_usage_percent as f64 / 100.0);
160 let mem_score = 1.0 - (resources.memory_usage_percent as f64 / 100.0);
161 let resource_score = (cpu_score + mem_score) / 2.0;
162 score += resource_score * weights.resources;
163
164 let battery_score = resources
165 .battery_percent
166 .map(|b| b as f64 / 100.0)
167 .unwrap_or(1.0);
168 score += battery_score * weights.battery;
169 } else {
170 score += 0.5 * (weights.resources + weights.battery); }
172
173 if beacon.can_parent {
175 score *= 1.1;
176 }
177 score *= 1.0 + (beacon.parent_priority as f64 / 255.0);
178
179 score
180 }
181}
182
183impl HierarchyStrategy for DynamicHierarchyStrategy {
184 fn determine_level(&self, _node_profile: &NodeProfile) -> HierarchyLevel {
185 self.base_level
188 }
189
190 fn determine_role(
191 &self,
192 node_profile: &NodeProfile,
193 nearby_peers: &[GeographicBeacon],
194 ) -> NodeRole {
195 let same_level_peers: Vec<&GeographicBeacon> = nearby_peers
197 .iter()
198 .filter(|b| b.hierarchy_level == self.base_level)
199 .collect();
200
201 if same_level_peers.is_empty() {
202 return NodeRole::Standalone;
204 }
205
206 let my_score = self.calculate_leadership_score(node_profile);
208
209 let best_peer_score = same_level_peers
211 .iter()
212 .map(|p| self.calculate_leadership_score_from_beacon(p))
213 .max_by(|a, b| a.partial_cmp(b).unwrap())
214 .unwrap_or(0.0);
215
216 let threshold = best_peer_score * (1.0 + self.election_config.hysteresis);
218
219 if my_score >= threshold {
220 NodeRole::Leader
221 } else {
222 NodeRole::Member
223 }
224 }
225
226 fn can_transition(&self, _current_level: HierarchyLevel, _new_level: HierarchyLevel) -> bool {
227 self.allow_level_transitions
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::beacon::{GeoPosition, NodeResources};
236
237 fn create_high_capability_profile() -> NodeProfile {
238 NodeProfile {
239 mobility: NodeMobility::Static,
240 resources: NodeResources {
241 cpu_cores: 8,
242 memory_mb: 8192,
243 bandwidth_mbps: 1000,
244 cpu_usage_percent: 20,
245 memory_usage_percent: 30,
246 battery_percent: None, },
248 can_parent: true,
249 prefer_leaf: false,
250 parent_priority: 200,
251 }
252 }
253
254 fn create_low_capability_profile() -> NodeProfile {
255 NodeProfile {
256 mobility: NodeMobility::Mobile,
257 resources: NodeResources {
258 cpu_cores: 2,
259 memory_mb: 1024,
260 bandwidth_mbps: 50,
261 cpu_usage_percent: 70,
262 memory_usage_percent: 80,
263 battery_percent: Some(30),
264 },
265 can_parent: false,
266 prefer_leaf: true,
267 parent_priority: 50,
268 }
269 }
270
271 #[test]
272 fn test_leadership_score_prefers_high_capability() {
273 let strategy =
274 DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
275
276 let high_cap = create_high_capability_profile();
277 let low_cap = create_low_capability_profile();
278
279 let high_score = strategy.calculate_leadership_score(&high_cap);
280 let low_score = strategy.calculate_leadership_score(&low_cap);
281
282 assert!(high_score > low_score);
283 }
284
285 #[test]
286 fn test_role_determination_standalone_when_no_peers() {
287 let strategy =
288 DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
289
290 let profile = create_high_capability_profile();
291 let role = strategy.determine_role(&profile, &[]);
292
293 assert_eq!(role, NodeRole::Standalone);
294 }
295
296 #[test]
297 fn test_role_determination_leader_with_high_capability() {
298 let strategy =
299 DynamicHierarchyStrategy::new(HierarchyLevel::Squad, ElectionConfig::default(), false);
300
301 let high_cap = create_high_capability_profile();
302
303 let mut low_cap_beacon = GeographicBeacon::new(
305 "peer1".to_string(),
306 GeoPosition::new(37.7750, -122.4195),
307 HierarchyLevel::Squad,
308 );
309 low_cap_beacon.mobility = Some(NodeMobility::Mobile);
310 low_cap_beacon.resources = Some(NodeResources {
311 cpu_cores: 2,
312 memory_mb: 1024,
313 bandwidth_mbps: 50,
314 cpu_usage_percent: 70,
315 memory_usage_percent: 80,
316 battery_percent: Some(30),
317 });
318 low_cap_beacon.can_parent = false;
319 low_cap_beacon.parent_priority = 50;
320
321 let role = strategy.determine_role(&high_cap, &[low_cap_beacon]);
322
323 assert_eq!(role, NodeRole::Leader);
324 }
325
326 #[test]
327 fn test_level_transitions_allowed_when_configured() {
328 let strategy = DynamicHierarchyStrategy::new(
329 HierarchyLevel::Squad,
330 ElectionConfig::default(),
331 true, );
333
334 assert!(strategy.can_transition(HierarchyLevel::Squad, HierarchyLevel::Platoon));
335 }
336
337 #[test]
338 fn test_level_transitions_disabled_when_configured() {
339 let strategy = DynamicHierarchyStrategy::new(
340 HierarchyLevel::Squad,
341 ElectionConfig::default(),
342 false, );
344
345 assert!(!strategy.can_transition(HierarchyLevel::Squad, HierarchyLevel::Platoon));
346 }
347}