1use crate::cell::{AggregatedCapability, CapabilityAggregator};
26use crate::models::{CellRole, NodeConfig, NodeState};
27use crate::traits::Phase;
28use crate::{Error, Result};
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::time::{SystemTime, UNIX_EPOCH};
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub enum FormationStatus {
36 Forming,
38 AwaitingApproval,
40 Ready,
42 Failed(String),
44}
45
46pub struct CellCoordinator {
48 pub squad_id: String,
50 pub min_size: usize,
52 pub min_readiness: f32,
54 pub required_capabilities: Vec<crate::models::CapabilityType>,
56 pub status: FormationStatus,
58 pub human_approved: bool,
60 pub formation_start: u64,
62 pub formation_complete: Option<u64>,
64}
65
66impl CellCoordinator {
67 pub fn new(squad_id: String) -> Self {
69 let now = SystemTime::now()
70 .duration_since(UNIX_EPOCH)
71 .unwrap()
72 .as_secs();
73
74 Self {
75 squad_id,
76 min_size: 3,
77 min_readiness: 0.7,
78 required_capabilities: vec![
79 crate::models::CapabilityType::Communication,
80 crate::models::CapabilityType::Sensor,
81 ],
82 status: FormationStatus::Forming,
83 human_approved: false,
84 formation_start: now,
85 formation_complete: None,
86 }
87 }
88
89 pub fn check_formation_complete(
98 &mut self,
99 members: &[(NodeConfig, NodeState, Option<CellRole>)],
100 leader_id: Option<&str>,
101 ) -> Result<bool> {
102 if members.len() < self.min_size {
104 self.status = FormationStatus::Failed(format!(
105 "Insufficient members: {} < {}",
106 members.len(),
107 self.min_size
108 ));
109 return Ok(false);
110 }
111
112 if leader_id.is_none() {
114 return Ok(false); }
116
117 let unassigned = members.iter().filter(|(_, _, role)| role.is_none()).count();
119 if unassigned > 0 {
120 return Ok(false); }
122
123 let members_for_agg: Vec<(NodeConfig, NodeState)> = members
125 .iter()
126 .map(|(c, s, _)| (c.clone(), s.clone()))
127 .collect();
128
129 let capabilities = CapabilityAggregator::aggregate_capabilities(&members_for_agg)?;
130
131 let gaps = CapabilityAggregator::identify_gaps(&capabilities, &self.required_capabilities);
133 if !gaps.is_empty() {
134 self.status =
135 FormationStatus::Failed(format!("Missing required capabilities: {:?}", gaps));
136 return Ok(false);
137 }
138
139 let readiness = CapabilityAggregator::calculate_readiness_score(&capabilities);
141 if readiness < self.min_readiness {
142 self.status = FormationStatus::Failed(format!(
143 "Insufficient readiness: {:.2} < {:.2}",
144 readiness, self.min_readiness
145 ));
146 return Ok(false);
147 }
148
149 let needs_approval = self.needs_human_approval(&capabilities);
151
152 if needs_approval && !self.human_approved {
153 self.status = FormationStatus::AwaitingApproval;
154 return Ok(false); }
156
157 self.status = FormationStatus::Ready;
159 if self.formation_complete.is_none() {
160 self.formation_complete = Some(
161 SystemTime::now()
162 .duration_since(UNIX_EPOCH)
163 .unwrap()
164 .as_secs(),
165 );
166 }
167
168 Ok(true)
169 }
170
171 fn needs_human_approval(
175 &self,
176 capabilities: &HashMap<crate::models::CapabilityType, AggregatedCapability>,
177 ) -> bool {
178 capabilities.values().any(|cap| cap.requires_oversight)
179 }
180
181 pub fn approve_formation(&mut self) -> Result<()> {
183 if self.status != FormationStatus::AwaitingApproval {
184 return Err(Error::InvalidTransition {
185 from: format!("{:?}", self.status),
186 to: "Ready".to_string(),
187 reason: "Cannot approve formation not awaiting approval".to_string(),
188 });
189 }
190
191 self.human_approved = true;
192 self.status = FormationStatus::Ready;
193
194 if self.formation_complete.is_none() {
195 self.formation_complete = Some(
196 SystemTime::now()
197 .duration_since(UNIX_EPOCH)
198 .unwrap()
199 .as_secs(),
200 );
201 }
202
203 Ok(())
204 }
205
206 pub fn reject_formation(&mut self, reason: String) -> Result<()> {
208 if self.status != FormationStatus::AwaitingApproval {
209 return Err(Error::InvalidTransition {
210 from: format!("{:?}", self.status),
211 to: "Failed".to_string(),
212 reason: "Cannot reject formation not awaiting approval".to_string(),
213 });
214 }
215
216 self.status = FormationStatus::Failed(format!("Human rejected: {}", reason));
217 Ok(())
218 }
219
220 pub fn formation_duration(&self) -> u64 {
222 let end = self.formation_complete.unwrap_or_else(|| {
223 SystemTime::now()
224 .duration_since(UNIX_EPOCH)
225 .unwrap()
226 .as_secs()
227 });
228
229 end - self.formation_start
230 }
231
232 pub fn can_transition_to_hierarchical(&self) -> bool {
234 self.status == FormationStatus::Ready
235 }
236
237 pub fn get_hierarchical_phase(&self) -> Result<Phase> {
239 if !self.can_transition_to_hierarchical() {
240 return Err(Error::InvalidTransition {
241 from: "Squad".to_string(),
242 to: "Hierarchical".to_string(),
243 reason: format!("Cannot transition with status: {:?}", self.status),
244 });
245 }
246
247 Ok(Phase::Hierarchy)
248 }
249
250 pub fn reset(&mut self) {
252 self.status = FormationStatus::Forming;
253 self.human_approved = false;
254 self.formation_complete = None;
255 self.formation_start = SystemTime::now()
256 .duration_since(UNIX_EPOCH)
257 .unwrap()
258 .as_secs();
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::models::{
266 AuthorityLevel, Capability, CapabilityExt, CapabilityType, HumanMachinePair,
267 HumanMachinePairExt, NodeConfigExt, NodeStateExt, Operator, OperatorExt, OperatorRank,
268 };
269
270 fn create_test_member(
271 id: &str,
272 capabilities: Vec<CapabilityType>,
273 role: Option<CellRole>,
274 operator: Option<Operator>,
275 ) -> (NodeConfig, NodeState, Option<CellRole>) {
276 let mut config = NodeConfig::new("Test".to_string());
277 config.id = id.to_string();
278
279 for cap_type in capabilities {
280 config.add_capability(Capability::new(
281 format!("{}_{:?}", id, cap_type),
282 format!("{:?}", cap_type),
283 cap_type,
284 0.9,
285 ));
286 }
287
288 if let Some(op) = operator {
289 let binding = HumanMachinePair::new(
290 vec![op],
291 vec![id.to_string()],
292 crate::models::BindingType::OneToOne,
293 );
294 config.operator_binding = Some(binding);
295 }
296
297 let state = NodeState::new((0.0, 0.0, 0.0));
298
299 (config, state, role)
300 }
301
302 #[test]
303 fn test_coordinator_creation() {
304 let coord = CellCoordinator::new("cell1".to_string());
305 assert_eq!(coord.squad_id, "cell1");
306 assert_eq!(coord.status, FormationStatus::Forming);
307 assert_eq!(coord.min_size, 3);
308 assert!(!coord.human_approved);
309 }
310
311 #[test]
312 fn test_insufficient_members() {
313 let mut coord = CellCoordinator::new("cell1".to_string());
314
315 let members = vec![
316 create_test_member(
317 "p1",
318 vec![CapabilityType::Communication, CapabilityType::Sensor],
319 Some(CellRole::Leader),
320 None,
321 ),
322 create_test_member(
323 "p2",
324 vec![CapabilityType::Sensor],
325 Some(CellRole::Sensor),
326 None,
327 ),
328 ];
329
330 let complete = coord
331 .check_formation_complete(&members, Some("p1"))
332 .unwrap();
333
334 assert!(!complete);
335 assert!(matches!(
336 coord.status,
337 FormationStatus::Failed(ref msg) if msg.contains("Insufficient members")
338 ));
339 }
340
341 #[test]
342 fn test_no_leader() {
343 let mut coord = CellCoordinator::new("cell1".to_string());
344
345 let members = vec![
346 create_test_member(
347 "p1",
348 vec![CapabilityType::Communication],
349 Some(CellRole::Follower),
350 None,
351 ),
352 create_test_member(
353 "p2",
354 vec![CapabilityType::Sensor],
355 Some(CellRole::Sensor),
356 None,
357 ),
358 create_test_member(
359 "p3",
360 vec![CapabilityType::Compute],
361 Some(CellRole::Compute),
362 None,
363 ),
364 ];
365
366 let complete = coord.check_formation_complete(&members, None).unwrap();
367
368 assert!(!complete);
369 assert_eq!(coord.status, FormationStatus::Forming); }
371
372 #[test]
373 fn test_unassigned_roles() {
374 let mut coord = CellCoordinator::new("cell1".to_string());
375
376 let members = vec![
377 create_test_member(
378 "p1",
379 vec![CapabilityType::Communication],
380 Some(CellRole::Leader),
381 None,
382 ),
383 create_test_member(
384 "p2",
385 vec![CapabilityType::Sensor],
386 Some(CellRole::Sensor),
387 None,
388 ),
389 create_test_member("p3", vec![CapabilityType::Compute], None, None), ];
391
392 let complete = coord
393 .check_formation_complete(&members, Some("p1"))
394 .unwrap();
395
396 assert!(!complete);
397 assert_eq!(coord.status, FormationStatus::Forming);
398 }
399
400 #[test]
401 fn test_missing_required_capabilities() {
402 let mut coord = CellCoordinator::new("cell1".to_string());
403
404 let members = vec![
405 create_test_member(
406 "p1",
407 vec![CapabilityType::Communication],
408 Some(CellRole::Leader),
409 None,
410 ),
411 create_test_member(
412 "p2",
413 vec![CapabilityType::Compute],
414 Some(CellRole::Compute),
415 None,
416 ), create_test_member(
418 "p3",
419 vec![CapabilityType::Mobility],
420 Some(CellRole::Follower),
421 None,
422 ),
423 ];
424
425 let complete = coord
426 .check_formation_complete(&members, Some("p1"))
427 .unwrap();
428
429 assert!(!complete);
430 assert!(matches!(
431 coord.status,
432 FormationStatus::Failed(ref msg) if msg.contains("Missing required capabilities")
433 ));
434 }
435
436 #[test]
437 fn test_formation_complete_no_approval_needed() {
438 let mut coord = CellCoordinator::new("cell1".to_string());
439
440 let operator = Operator::new(
442 "op1".to_string(),
443 "Test Operator".to_string(),
444 OperatorRank::E5,
445 AuthorityLevel::Commander,
446 "11B".to_string(),
447 );
448
449 let members = vec![
450 create_test_member(
451 "p1",
452 vec![CapabilityType::Communication, CapabilityType::Sensor],
453 Some(CellRole::Leader),
454 Some(operator),
455 ),
456 create_test_member(
457 "p2",
458 vec![CapabilityType::Sensor],
459 Some(CellRole::Sensor),
460 None,
461 ),
462 create_test_member(
463 "p3",
464 vec![CapabilityType::Compute],
465 Some(CellRole::Compute),
466 None,
467 ),
468 ];
469
470 let complete = coord
471 .check_formation_complete(&members, Some("p1"))
472 .unwrap();
473
474 assert!(complete);
475 assert_eq!(coord.status, FormationStatus::Ready);
476 assert!(coord.formation_complete.is_some());
477 }
478
479 #[test]
480 fn test_formation_awaiting_approval() {
481 let mut coord = CellCoordinator::new("cell1".to_string());
482
483 let operator1 = Operator::new(
485 "op1".to_string(),
486 "Test Operator 1".to_string(),
487 OperatorRank::E5,
488 AuthorityLevel::Commander,
489 "11B".to_string(),
490 );
491
492 let operator3 = Operator::new(
494 "op3".to_string(),
495 "Test Operator 3".to_string(),
496 OperatorRank::E5,
497 AuthorityLevel::Observer, "11B".to_string(),
499 );
500
501 let members = vec![
502 create_test_member(
503 "p1",
504 vec![CapabilityType::Communication, CapabilityType::Sensor],
505 Some(CellRole::Leader),
506 Some(operator1),
507 ),
508 create_test_member(
509 "p2",
510 vec![CapabilityType::Sensor],
511 Some(CellRole::Sensor),
512 None,
513 ),
514 create_test_member(
515 "p3",
516 vec![CapabilityType::Payload], Some(CellRole::Follower),
518 Some(operator3),
519 ),
520 ];
521
522 let complete = coord
523 .check_formation_complete(&members, Some("p1"))
524 .unwrap();
525
526 assert!(!complete); assert_eq!(coord.status, FormationStatus::AwaitingApproval);
528 }
529
530 #[test]
531 fn test_human_approval_workflow() {
532 let mut coord = CellCoordinator::new("cell1".to_string());
533 coord.status = FormationStatus::AwaitingApproval;
534
535 coord.approve_formation().unwrap();
536
537 assert_eq!(coord.status, FormationStatus::Ready);
538 assert!(coord.human_approved);
539 assert!(coord.formation_complete.is_some());
540 }
541
542 #[test]
543 fn test_human_rejection() {
544 let mut coord = CellCoordinator::new("cell1".to_string());
545 coord.status = FormationStatus::AwaitingApproval;
546
547 coord
548 .reject_formation("Insufficient capability coverage".to_string())
549 .unwrap();
550
551 assert!(matches!(
552 coord.status,
553 FormationStatus::Failed(ref msg) if msg.contains("Human rejected")
554 ));
555 }
556
557 #[test]
558 fn test_phase_transition() {
559 let mut coord = CellCoordinator::new("cell1".to_string());
560 coord.status = FormationStatus::Ready;
561
562 assert!(coord.can_transition_to_hierarchical());
563
564 let phase = coord.get_hierarchical_phase().unwrap();
565 assert_eq!(phase, Phase::Hierarchy);
566 }
567
568 #[test]
569 fn test_cannot_transition_when_not_ready() {
570 let coord = CellCoordinator::new("cell1".to_string());
571
572 assert!(!coord.can_transition_to_hierarchical());
573 assert!(coord.get_hierarchical_phase().is_err());
574 }
575
576 #[test]
577 fn test_formation_duration() {
578 let coord = CellCoordinator::new("cell1".to_string());
579
580 std::thread::sleep(std::time::Duration::from_secs(1));
581
582 let duration = coord.formation_duration();
583 assert!(duration >= 1);
584 }
585
586 #[test]
587 fn test_reset_formation() {
588 let mut coord = CellCoordinator::new("cell1".to_string());
589 coord.status = FormationStatus::Ready;
590 coord.human_approved = true;
591 coord.formation_complete = Some(12345);
592
593 coord.reset();
594
595 assert_eq!(coord.status, FormationStatus::Forming);
596 assert!(!coord.human_approved);
597 assert!(coord.formation_complete.is_none());
598 }
599
600 #[test]
601 fn test_single_member_squad() {
602 let mut coord = CellCoordinator::new("cell1".to_string());
604
605 let operator = Operator::new(
606 "op1".to_string(),
607 "Test Operator".to_string(),
608 OperatorRank::E5,
609 AuthorityLevel::Commander,
610 "11B".to_string(),
611 );
612
613 let members = vec![create_test_member(
614 "p1",
615 vec![CapabilityType::Communication, CapabilityType::Sensor],
616 Some(CellRole::Leader),
617 Some(operator),
618 )];
619
620 let complete = coord
621 .check_formation_complete(&members, Some("p1"))
622 .unwrap();
623
624 assert!(!complete);
625 assert!(matches!(
626 coord.status,
627 FormationStatus::Failed(ref msg) if msg.contains("Insufficient members")
628 ));
629 }
630
631 #[test]
632 fn test_exact_minimum_size_squad() {
633 let mut coord = CellCoordinator::new("cell1".to_string());
635 assert_eq!(coord.min_size, 3);
636
637 let operator = Operator::new(
638 "op1".to_string(),
639 "Test Operator".to_string(),
640 OperatorRank::E5,
641 AuthorityLevel::Commander,
642 "11B".to_string(),
643 );
644
645 let members = vec![
646 create_test_member(
647 "p1",
648 vec![CapabilityType::Communication, CapabilityType::Sensor],
649 Some(CellRole::Leader),
650 Some(operator),
651 ),
652 create_test_member(
653 "p2",
654 vec![CapabilityType::Sensor],
655 Some(CellRole::Sensor),
656 None,
657 ),
658 create_test_member(
659 "p3",
660 vec![CapabilityType::Compute],
661 Some(CellRole::Compute),
662 None,
663 ),
664 ];
665
666 let complete = coord
667 .check_formation_complete(&members, Some("p1"))
668 .unwrap();
669
670 assert!(complete);
672 assert_eq!(coord.status, FormationStatus::Ready);
673 }
674
675 #[test]
676 fn test_empty_squad_formation() {
677 let mut coord = CellCoordinator::new("cell1".to_string());
679
680 let complete = coord.check_formation_complete(&[], None).unwrap();
681
682 assert!(!complete);
683 assert!(matches!(
684 coord.status,
685 FormationStatus::Failed(ref msg) if msg.contains("Insufficient members: 0 < 3")
686 ));
687 }
688
689 #[test]
690 fn test_approval_idempotency() {
691 let mut coord = CellCoordinator::new("cell1".to_string());
693 coord.status = FormationStatus::AwaitingApproval;
694
695 coord.approve_formation().unwrap();
697 assert_eq!(coord.status, FormationStatus::Ready);
698 assert!(coord.human_approved);
699
700 let result = coord.approve_formation();
702 assert!(result.is_err());
703 }
704}