use crate::cell::{AggregatedCapability, CapabilityAggregator};
use crate::models::{CellRole, NodeConfig, NodeState};
use crate::traits::Phase;
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FormationStatus {
Forming,
AwaitingApproval,
Ready,
Failed(String),
}
pub struct CellCoordinator {
pub squad_id: String,
pub min_size: usize,
pub min_readiness: f32,
pub required_capabilities: Vec<crate::models::CapabilityType>,
pub status: FormationStatus,
pub human_approved: bool,
pub formation_start: u64,
pub formation_complete: Option<u64>,
}
impl CellCoordinator {
pub fn new(squad_id: String) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
squad_id,
min_size: 3,
min_readiness: 0.7,
required_capabilities: vec![
crate::models::CapabilityType::Communication,
crate::models::CapabilityType::Sensor,
],
status: FormationStatus::Forming,
human_approved: false,
formation_start: now,
formation_complete: None,
}
}
pub fn check_formation_complete(
&mut self,
members: &[(NodeConfig, NodeState, Option<CellRole>)],
leader_id: Option<&str>,
) -> Result<bool> {
if members.len() < self.min_size {
self.status = FormationStatus::Failed(format!(
"Insufficient members: {} < {}",
members.len(),
self.min_size
));
return Ok(false);
}
if leader_id.is_none() {
return Ok(false); }
let unassigned = members.iter().filter(|(_, _, role)| role.is_none()).count();
if unassigned > 0 {
return Ok(false); }
let members_for_agg: Vec<(NodeConfig, NodeState)> = members
.iter()
.map(|(c, s, _)| (c.clone(), s.clone()))
.collect();
let capabilities = CapabilityAggregator::aggregate_capabilities(&members_for_agg)?;
let gaps = CapabilityAggregator::identify_gaps(&capabilities, &self.required_capabilities);
if !gaps.is_empty() {
self.status =
FormationStatus::Failed(format!("Missing required capabilities: {:?}", gaps));
return Ok(false);
}
let readiness = CapabilityAggregator::calculate_readiness_score(&capabilities);
if readiness < self.min_readiness {
self.status = FormationStatus::Failed(format!(
"Insufficient readiness: {:.2} < {:.2}",
readiness, self.min_readiness
));
return Ok(false);
}
let needs_approval = self.needs_human_approval(&capabilities);
if needs_approval && !self.human_approved {
self.status = FormationStatus::AwaitingApproval;
return Ok(false); }
self.status = FormationStatus::Ready;
if self.formation_complete.is_none() {
self.formation_complete = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
);
}
Ok(true)
}
fn needs_human_approval(
&self,
capabilities: &HashMap<crate::models::CapabilityType, AggregatedCapability>,
) -> bool {
capabilities.values().any(|cap| cap.requires_oversight)
}
pub fn approve_formation(&mut self) -> Result<()> {
if self.status != FormationStatus::AwaitingApproval {
return Err(Error::InvalidTransition {
from: format!("{:?}", self.status),
to: "Ready".to_string(),
reason: "Cannot approve formation not awaiting approval".to_string(),
});
}
self.human_approved = true;
self.status = FormationStatus::Ready;
if self.formation_complete.is_none() {
self.formation_complete = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
);
}
Ok(())
}
pub fn reject_formation(&mut self, reason: String) -> Result<()> {
if self.status != FormationStatus::AwaitingApproval {
return Err(Error::InvalidTransition {
from: format!("{:?}", self.status),
to: "Failed".to_string(),
reason: "Cannot reject formation not awaiting approval".to_string(),
});
}
self.status = FormationStatus::Failed(format!("Human rejected: {}", reason));
Ok(())
}
pub fn formation_duration(&self) -> u64 {
let end = self.formation_complete.unwrap_or_else(|| {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
});
end - self.formation_start
}
pub fn can_transition_to_hierarchical(&self) -> bool {
self.status == FormationStatus::Ready
}
pub fn get_hierarchical_phase(&self) -> Result<Phase> {
if !self.can_transition_to_hierarchical() {
return Err(Error::InvalidTransition {
from: "Squad".to_string(),
to: "Hierarchical".to_string(),
reason: format!("Cannot transition with status: {:?}", self.status),
});
}
Ok(Phase::Hierarchy)
}
pub fn reset(&mut self) {
self.status = FormationStatus::Forming;
self.human_approved = false;
self.formation_complete = None;
self.formation_start = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{
AuthorityLevel, Capability, CapabilityExt, CapabilityType, HumanMachinePair,
HumanMachinePairExt, NodeConfigExt, NodeStateExt, Operator, OperatorExt, OperatorRank,
};
fn create_test_member(
id: &str,
capabilities: Vec<CapabilityType>,
role: Option<CellRole>,
operator: Option<Operator>,
) -> (NodeConfig, NodeState, Option<CellRole>) {
let mut config = NodeConfig::new("Test".to_string());
config.id = id.to_string();
for cap_type in capabilities {
config.add_capability(Capability::new(
format!("{}_{:?}", id, cap_type),
format!("{:?}", cap_type),
cap_type,
0.9,
));
}
if let Some(op) = operator {
let binding = HumanMachinePair::new(
vec![op],
vec![id.to_string()],
crate::models::BindingType::OneToOne,
);
config.operator_binding = Some(binding);
}
let state = NodeState::new((0.0, 0.0, 0.0));
(config, state, role)
}
#[test]
fn test_coordinator_creation() {
let coord = CellCoordinator::new("cell1".to_string());
assert_eq!(coord.squad_id, "cell1");
assert_eq!(coord.status, FormationStatus::Forming);
assert_eq!(coord.min_size, 3);
assert!(!coord.human_approved);
}
#[test]
fn test_insufficient_members() {
let mut coord = CellCoordinator::new("cell1".to_string());
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication, CapabilityType::Sensor],
Some(CellRole::Leader),
None,
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(!complete);
assert!(matches!(
coord.status,
FormationStatus::Failed(ref msg) if msg.contains("Insufficient members")
));
}
#[test]
fn test_no_leader() {
let mut coord = CellCoordinator::new("cell1".to_string());
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication],
Some(CellRole::Follower),
None,
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
create_test_member(
"p3",
vec![CapabilityType::Compute],
Some(CellRole::Compute),
None,
),
];
let complete = coord.check_formation_complete(&members, None).unwrap();
assert!(!complete);
assert_eq!(coord.status, FormationStatus::Forming); }
#[test]
fn test_unassigned_roles() {
let mut coord = CellCoordinator::new("cell1".to_string());
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication],
Some(CellRole::Leader),
None,
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
create_test_member("p3", vec![CapabilityType::Compute], None, None), ];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(!complete);
assert_eq!(coord.status, FormationStatus::Forming);
}
#[test]
fn test_missing_required_capabilities() {
let mut coord = CellCoordinator::new("cell1".to_string());
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication],
Some(CellRole::Leader),
None,
),
create_test_member(
"p2",
vec![CapabilityType::Compute],
Some(CellRole::Compute),
None,
), create_test_member(
"p3",
vec![CapabilityType::Mobility],
Some(CellRole::Follower),
None,
),
];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(!complete);
assert!(matches!(
coord.status,
FormationStatus::Failed(ref msg) if msg.contains("Missing required capabilities")
));
}
#[test]
fn test_formation_complete_no_approval_needed() {
let mut coord = CellCoordinator::new("cell1".to_string());
let operator = Operator::new(
"op1".to_string(),
"Test Operator".to_string(),
OperatorRank::E5,
AuthorityLevel::Commander,
"11B".to_string(),
);
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication, CapabilityType::Sensor],
Some(CellRole::Leader),
Some(operator),
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
create_test_member(
"p3",
vec![CapabilityType::Compute],
Some(CellRole::Compute),
None,
),
];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(complete);
assert_eq!(coord.status, FormationStatus::Ready);
assert!(coord.formation_complete.is_some());
}
#[test]
fn test_formation_awaiting_approval() {
let mut coord = CellCoordinator::new("cell1".to_string());
let operator1 = Operator::new(
"op1".to_string(),
"Test Operator 1".to_string(),
OperatorRank::E5,
AuthorityLevel::Commander,
"11B".to_string(),
);
let operator3 = Operator::new(
"op3".to_string(),
"Test Operator 3".to_string(),
OperatorRank::E5,
AuthorityLevel::Observer, "11B".to_string(),
);
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication, CapabilityType::Sensor],
Some(CellRole::Leader),
Some(operator1),
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
create_test_member(
"p3",
vec![CapabilityType::Payload], Some(CellRole::Follower),
Some(operator3),
),
];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(!complete); assert_eq!(coord.status, FormationStatus::AwaitingApproval);
}
#[test]
fn test_human_approval_workflow() {
let mut coord = CellCoordinator::new("cell1".to_string());
coord.status = FormationStatus::AwaitingApproval;
coord.approve_formation().unwrap();
assert_eq!(coord.status, FormationStatus::Ready);
assert!(coord.human_approved);
assert!(coord.formation_complete.is_some());
}
#[test]
fn test_human_rejection() {
let mut coord = CellCoordinator::new("cell1".to_string());
coord.status = FormationStatus::AwaitingApproval;
coord
.reject_formation("Insufficient capability coverage".to_string())
.unwrap();
assert!(matches!(
coord.status,
FormationStatus::Failed(ref msg) if msg.contains("Human rejected")
));
}
#[test]
fn test_phase_transition() {
let mut coord = CellCoordinator::new("cell1".to_string());
coord.status = FormationStatus::Ready;
assert!(coord.can_transition_to_hierarchical());
let phase = coord.get_hierarchical_phase().unwrap();
assert_eq!(phase, Phase::Hierarchy);
}
#[test]
fn test_cannot_transition_when_not_ready() {
let coord = CellCoordinator::new("cell1".to_string());
assert!(!coord.can_transition_to_hierarchical());
assert!(coord.get_hierarchical_phase().is_err());
}
#[test]
fn test_formation_duration() {
let coord = CellCoordinator::new("cell1".to_string());
std::thread::sleep(std::time::Duration::from_secs(1));
let duration = coord.formation_duration();
assert!(duration >= 1);
}
#[test]
fn test_reset_formation() {
let mut coord = CellCoordinator::new("cell1".to_string());
coord.status = FormationStatus::Ready;
coord.human_approved = true;
coord.formation_complete = Some(12345);
coord.reset();
assert_eq!(coord.status, FormationStatus::Forming);
assert!(!coord.human_approved);
assert!(coord.formation_complete.is_none());
}
#[test]
fn test_single_member_squad() {
let mut coord = CellCoordinator::new("cell1".to_string());
let operator = Operator::new(
"op1".to_string(),
"Test Operator".to_string(),
OperatorRank::E5,
AuthorityLevel::Commander,
"11B".to_string(),
);
let members = vec![create_test_member(
"p1",
vec![CapabilityType::Communication, CapabilityType::Sensor],
Some(CellRole::Leader),
Some(operator),
)];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(!complete);
assert!(matches!(
coord.status,
FormationStatus::Failed(ref msg) if msg.contains("Insufficient members")
));
}
#[test]
fn test_exact_minimum_size_squad() {
let mut coord = CellCoordinator::new("cell1".to_string());
assert_eq!(coord.min_size, 3);
let operator = Operator::new(
"op1".to_string(),
"Test Operator".to_string(),
OperatorRank::E5,
AuthorityLevel::Commander,
"11B".to_string(),
);
let members = vec![
create_test_member(
"p1",
vec![CapabilityType::Communication, CapabilityType::Sensor],
Some(CellRole::Leader),
Some(operator),
),
create_test_member(
"p2",
vec![CapabilityType::Sensor],
Some(CellRole::Sensor),
None,
),
create_test_member(
"p3",
vec![CapabilityType::Compute],
Some(CellRole::Compute),
None,
),
];
let complete = coord
.check_formation_complete(&members, Some("p1"))
.unwrap();
assert!(complete);
assert_eq!(coord.status, FormationStatus::Ready);
}
#[test]
fn test_empty_squad_formation() {
let mut coord = CellCoordinator::new("cell1".to_string());
let complete = coord.check_formation_complete(&[], None).unwrap();
assert!(!complete);
assert!(matches!(
coord.status,
FormationStatus::Failed(ref msg) if msg.contains("Insufficient members: 0 < 3")
));
}
#[test]
fn test_approval_idempotency() {
let mut coord = CellCoordinator::new("cell1".to_string());
coord.status = FormationStatus::AwaitingApproval;
coord.approve_formation().unwrap();
assert_eq!(coord.status, FormationStatus::Ready);
assert!(coord.human_approved);
let result = coord.approve_formation();
assert!(result.is_err());
}
}