use crate::models::{Capability, CapabilityExt, CapabilityType, CellState};
use crate::Result;
use std::time::{Duration, Instant};
use tracing::{debug, info, instrument};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZoneFormationStatus {
Forming,
Ready,
Degraded,
}
pub struct ZoneCoordinator {
pub zone_id: String,
pub min_cells: usize,
pub min_readiness: f32,
pub status: ZoneFormationStatus,
formation_start: Instant,
}
impl ZoneCoordinator {
pub fn new(zone_id: String, min_cells: usize, min_readiness: f32) -> Self {
Self {
zone_id,
min_cells,
min_readiness: min_readiness.clamp(0.0, 1.0),
status: ZoneFormationStatus::Forming,
formation_start: Instant::now(),
}
}
#[instrument(skip(self, cells))]
pub fn check_formation_complete(
&mut self,
cells: &[CellState],
coordinator_id: Option<&str>,
) -> Result<bool> {
let cell_count = cells.len();
if cell_count < self.min_cells {
debug!(
"Zone {} forming: {}/{} cells",
self.zone_id, cell_count, self.min_cells
);
self.status = ZoneFormationStatus::Forming;
return Ok(false);
}
if coordinator_id.is_none() {
debug!("Zone {} forming: No coordinator assigned", self.zone_id);
self.status = ZoneFormationStatus::Forming;
return Ok(false);
}
let avg_readiness = self.calculate_average_readiness(cells);
if avg_readiness < self.min_readiness {
debug!(
"Zone {} forming: Readiness {:.2} < {:.2}",
self.zone_id, avg_readiness, self.min_readiness
);
self.status = ZoneFormationStatus::Forming;
return Ok(false);
}
info!(
"Zone {} ready: {} cells, {:.2} readiness",
self.zone_id, cell_count, avg_readiness
);
self.status = ZoneFormationStatus::Ready;
Ok(true)
}
fn calculate_average_readiness(&self, cells: &[CellState]) -> f32 {
if cells.is_empty() {
return 0.0;
}
let total: f32 = cells
.iter()
.map(|cell| {
let member_count = cell.members.len() as f32;
let max_size = cell
.config
.as_ref()
.map(|c| c.max_size as f32)
.unwrap_or(1.0);
member_count / max_size
})
.sum();
total / cells.len() as f32
}
#[instrument(skip(self, cells))]
pub fn aggregate_capabilities(&self, cells: &[CellState]) -> Vec<Capability> {
use std::collections::HashMap;
let mut capability_map: HashMap<CapabilityType, Capability> = HashMap::new();
for cell in cells {
for cap in &cell.capabilities {
capability_map
.entry(cap.get_capability_type())
.and_modify(|existing| {
if cap.confidence > existing.confidence {
*existing = cap.clone();
}
})
.or_insert_with(|| cap.clone());
}
}
let mut aggregated: Vec<Capability> = capability_map.into_values().collect();
aggregated.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
debug!(
"Zone {} aggregated {} capabilities from {} cells",
self.zone_id,
aggregated.len(),
cells.len()
);
aggregated
}
pub fn detect_emergent_capabilities(&self, cells: &[CellState]) -> Vec<Capability> {
use std::collections::HashSet;
if cells.len() < 2 {
return Vec::new();
}
let mut capability_types = HashSet::new();
for cell in cells {
for cap in &cell.capabilities {
capability_types.insert(cap.get_capability_type());
}
}
let mut emergent = Vec::new();
if capability_types.contains(&CapabilityType::Sensor)
&& capability_types.contains(&CapabilityType::Compute)
{
emergent.push(Capability::new(
format!("{}_emergent_isr", self.zone_id),
"Enhanced ISR Capability".to_string(),
CapabilityType::Emergent,
0.85, ));
}
if capability_types.contains(&CapabilityType::Communication)
&& capability_types.contains(&CapabilityType::Mobility)
&& cells.len() >= 3
{
emergent.push(Capability::new(
format!("{}_emergent_relay", self.zone_id),
"Mobile Relay Network".to_string(),
CapabilityType::Emergent,
0.80,
));
}
if capability_types.contains(&CapabilityType::Sensor)
&& capability_types.contains(&CapabilityType::Payload)
{
emergent.push(Capability::new(
format!("{}_emergent_strike", self.zone_id),
"Coordinated Strike Package".to_string(),
CapabilityType::Emergent,
0.75,
));
}
if !emergent.is_empty() {
info!(
"Zone {} detected {} emergent capabilities",
self.zone_id,
emergent.len()
);
}
emergent
}
pub fn can_transition_to_operations(&self) -> bool {
self.status == ZoneFormationStatus::Ready
&& self.formation_start.elapsed() >= Duration::from_secs(5)
}
pub fn get_metrics(&self, cells: &[CellState]) -> ZoneMetrics {
let total_nodes: usize = cells.iter().map(|cell| cell.members.len()).sum();
let aggregated_caps = self.aggregate_capabilities(cells);
let emergent_caps = self.detect_emergent_capabilities(cells);
ZoneMetrics {
cell_count: cells.len(),
total_nodes,
average_cell_readiness: self.calculate_average_readiness(cells),
capability_count: aggregated_caps.len(),
emergent_capability_count: emergent_caps.len(),
formation_time_ms: self.formation_start.elapsed().as_millis() as u64,
status: self.status,
}
}
pub fn update_status(&mut self, cells: &[CellState]) {
let cell_count = cells.len();
if cell_count < self.min_cells {
if self.status == ZoneFormationStatus::Ready {
info!(
"Zone {} degraded: {} < {} cells",
self.zone_id, cell_count, self.min_cells
);
self.status = ZoneFormationStatus::Degraded;
}
return;
}
let avg_readiness = self.calculate_average_readiness(cells);
if avg_readiness < self.min_readiness && self.status == ZoneFormationStatus::Ready {
info!(
"Zone {} degraded: {:.2} < {:.2} readiness",
self.zone_id, avg_readiness, self.min_readiness
);
self.status = ZoneFormationStatus::Degraded;
}
}
}
#[derive(Debug, Clone)]
pub struct ZoneMetrics {
pub cell_count: usize,
pub total_nodes: usize,
pub average_cell_readiness: f32,
pub capability_count: usize,
pub emergent_capability_count: usize,
pub formation_time_ms: u64,
pub status: ZoneFormationStatus,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{CellConfig, CellConfigExt, CellStateExt};
fn create_test_cell(id: &str, member_count: usize) -> CellState {
let mut config = CellConfig::new(10);
config.id = id.to_string();
let mut cell = CellState::new(config);
for i in 0..member_count {
cell.add_member(format!("{}_{}", id, i));
}
cell
}
fn add_capability(cell: &mut CellState, cap_type: CapabilityType, confidence: f32) {
let cell_id = cell.get_id().unwrap_or("unknown");
cell.capabilities.push(Capability::new(
format!("{}_{:?}", cell_id, cap_type),
format!("{:?} Capability", cap_type),
cap_type,
confidence,
));
}
#[test]
fn test_coordinator_creation() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 3, 0.8);
assert_eq!(coordinator.zone_id, "zone_1");
assert_eq!(coordinator.min_cells, 3);
assert_eq!(coordinator.min_readiness, 0.8);
assert_eq!(coordinator.status, ZoneFormationStatus::Forming);
}
#[test]
fn test_formation_incomplete_not_enough_cells() {
let mut coordinator = ZoneCoordinator::new("zone_1".to_string(), 3, 0.5);
let cells = vec![create_test_cell("cell_1", 5), create_test_cell("cell_2", 5)];
let is_complete = coordinator
.check_formation_complete(&cells, Some("node_1"))
.unwrap();
assert!(!is_complete);
assert_eq!(coordinator.status, ZoneFormationStatus::Forming);
}
#[test]
fn test_formation_incomplete_no_coordinator() {
let mut coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.5);
let cells = vec![create_test_cell("cell_1", 5), create_test_cell("cell_2", 5)];
let is_complete = coordinator.check_formation_complete(&cells, None).unwrap();
assert!(!is_complete);
assert_eq!(coordinator.status, ZoneFormationStatus::Forming);
}
#[test]
fn test_formation_incomplete_low_readiness() {
let mut coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.8);
let cells = vec![
create_test_cell("cell_1", 9), create_test_cell("cell_2", 3), ];
let is_complete = coordinator
.check_formation_complete(&cells, Some("node_1"))
.unwrap();
assert!(!is_complete); assert_eq!(coordinator.status, ZoneFormationStatus::Forming);
}
#[test]
fn test_formation_complete() {
let mut coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.4);
let cells = vec![
create_test_cell("cell_1", 5), create_test_cell("cell_2", 5), create_test_cell("cell_3", 4), ];
let is_complete = coordinator
.check_formation_complete(&cells, Some("node_1"))
.unwrap();
assert!(is_complete);
assert_eq!(coordinator.status, ZoneFormationStatus::Ready);
}
#[test]
fn test_capability_aggregation() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.5);
let mut cell1 = create_test_cell("cell_1", 5);
add_capability(&mut cell1, CapabilityType::Sensor, 0.9);
add_capability(&mut cell1, CapabilityType::Compute, 0.8);
let mut cell2 = create_test_cell("cell_2", 5);
add_capability(&mut cell2, CapabilityType::Sensor, 0.7); add_capability(&mut cell2, CapabilityType::Communication, 0.85);
let cells = vec![cell1, cell2];
let aggregated = coordinator.aggregate_capabilities(&cells);
assert_eq!(aggregated.len(), 3);
let sensor_cap = aggregated
.iter()
.find(|c| c.get_capability_type() == CapabilityType::Sensor)
.unwrap();
assert_eq!(sensor_cap.confidence, 0.9);
}
#[test]
fn test_emergent_capability_isr() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.5);
let mut cell1 = create_test_cell("cell_1", 5);
add_capability(&mut cell1, CapabilityType::Sensor, 0.9);
let mut cell2 = create_test_cell("cell_2", 5);
add_capability(&mut cell2, CapabilityType::Compute, 0.8);
let cells = vec![cell1, cell2];
let emergent = coordinator.detect_emergent_capabilities(&cells);
assert_eq!(emergent.len(), 1);
assert_eq!(emergent[0].get_capability_type(), CapabilityType::Emergent);
assert!(emergent[0].name.contains("ISR"));
}
#[test]
fn test_emergent_capability_relay_network() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 3, 0.5);
let mut cell1 = create_test_cell("cell_1", 5);
add_capability(&mut cell1, CapabilityType::Communication, 0.9);
let mut cell2 = create_test_cell("cell_2", 5);
add_capability(&mut cell2, CapabilityType::Mobility, 0.8);
let mut cell3 = create_test_cell("cell_3", 4);
add_capability(&mut cell3, CapabilityType::Communication, 0.85);
let cells = vec![cell1, cell2, cell3];
let emergent = coordinator.detect_emergent_capabilities(&cells);
assert!(!emergent.is_empty());
assert!(emergent
.iter()
.any(|c| c.name.contains("Relay") || c.name.contains("Network")));
}
#[test]
fn test_zone_metrics() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.5);
let mut cell1 = create_test_cell("cell_1", 5);
add_capability(&mut cell1, CapabilityType::Sensor, 0.9);
let mut cell2 = create_test_cell("cell_2", 4);
add_capability(&mut cell2, CapabilityType::Compute, 0.8);
let cells = vec![cell1, cell2];
let metrics = coordinator.get_metrics(&cells);
assert_eq!(metrics.cell_count, 2);
assert_eq!(metrics.total_nodes, 9); assert_eq!(metrics.average_cell_readiness, 0.45); assert_eq!(metrics.capability_count, 2);
assert_eq!(metrics.emergent_capability_count, 1); }
#[test]
fn test_zone_degradation() {
let mut coordinator = ZoneCoordinator::new("zone_1".to_string(), 3, 0.4);
let cells = vec![
create_test_cell("cell_1", 5),
create_test_cell("cell_2", 5),
create_test_cell("cell_3", 4),
];
coordinator
.check_formation_complete(&cells, Some("node_1"))
.unwrap();
assert_eq!(coordinator.status, ZoneFormationStatus::Ready);
let degraded_cells = vec![create_test_cell("cell_1", 5), create_test_cell("cell_2", 5)];
coordinator.update_status(°raded_cells);
assert_eq!(coordinator.status, ZoneFormationStatus::Degraded);
}
#[test]
fn test_can_transition_to_operations() {
let coordinator = ZoneCoordinator::new("zone_1".to_string(), 2, 0.7);
assert!(!coordinator.can_transition_to_operations());
}
}