Skip to main content

peat_protocol/models/
operator.rs

1//! Human operator and human-machine binding models
2//!
3//! This module defines the relationship between human operators and platforms,
4//! supporting multiple teaming patterns and authority models.
5
6// Re-export protobuf types
7pub use peat_schema::node::v1::{
8    AuthorityLevel, BindingType, HumanMachinePair, Operator, OperatorRank,
9};
10
11// Extension trait for Operator helper methods
12pub trait OperatorExt {
13    /// Create a new operator with default cognitive state
14    fn new(
15        id: String,
16        name: String,
17        rank: OperatorRank,
18        authority: AuthorityLevel,
19        mos: String,
20    ) -> Self;
21
22    /// Update cognitive load from metadata (clamped to 0.0-1.0)
23    fn update_cognitive_load(&mut self, load: f32);
24
25    /// Update fatigue from metadata (clamped to 0.0-1.0)
26    fn update_fatigue(&mut self, fatigue: f32);
27
28    /// Get cognitive load from metadata
29    fn cognitive_load(&self) -> f32;
30
31    /// Get fatigue from metadata
32    fn fatigue(&self) -> f32;
33
34    /// Check if operator is overloaded (cognitive load > threshold)
35    fn is_overloaded(&self, threshold: f32) -> bool;
36
37    /// Check if operator is fatigued (fatigue > threshold)
38    fn is_fatigued(&self, threshold: f32) -> bool;
39
40    /// Get operator effectiveness score (0.0-1.0)
41    /// Considers cognitive load and fatigue
42    fn effectiveness(&self) -> f32;
43}
44
45impl OperatorExt for Operator {
46    fn new(
47        id: String,
48        name: String,
49        rank: OperatorRank,
50        authority: AuthorityLevel,
51        mos: String,
52    ) -> Self {
53        use serde_json::json;
54        let metadata = json!({
55            "cognitive_load": 0.0,
56            "fatigue": 0.0,
57        });
58
59        Self {
60            id,
61            name,
62            rank: rank as i32,
63            authority_level: authority as i32,
64            mos,
65            metadata_json: metadata.to_string(),
66        }
67    }
68
69    fn update_cognitive_load(&mut self, load: f32) {
70        let load = load.clamp(0.0, 1.0);
71        let mut metadata: serde_json::Value =
72            serde_json::from_str(&self.metadata_json).unwrap_or(serde_json::json!({}));
73        metadata["cognitive_load"] = serde_json::json!(load);
74        self.metadata_json = metadata.to_string();
75    }
76
77    fn update_fatigue(&mut self, fatigue: f32) {
78        let fatigue = fatigue.clamp(0.0, 1.0);
79        let mut metadata: serde_json::Value =
80            serde_json::from_str(&self.metadata_json).unwrap_or(serde_json::json!({}));
81        metadata["fatigue"] = serde_json::json!(fatigue);
82        self.metadata_json = metadata.to_string();
83    }
84
85    fn cognitive_load(&self) -> f32 {
86        let metadata: serde_json::Value =
87            serde_json::from_str(&self.metadata_json).unwrap_or(serde_json::json!({}));
88        metadata["cognitive_load"].as_f64().unwrap_or(0.0) as f32
89    }
90
91    fn fatigue(&self) -> f32 {
92        let metadata: serde_json::Value =
93            serde_json::from_str(&self.metadata_json).unwrap_or(serde_json::json!({}));
94        metadata["fatigue"].as_f64().unwrap_or(0.0) as f32
95    }
96
97    fn is_overloaded(&self, threshold: f32) -> bool {
98        self.cognitive_load() > threshold
99    }
100
101    fn is_fatigued(&self, threshold: f32) -> bool {
102        self.fatigue() > threshold
103    }
104
105    fn effectiveness(&self) -> f32 {
106        let cognitive_factor = 1.0 - self.cognitive_load();
107        let fatigue_factor = 1.0 - self.fatigue();
108        (cognitive_factor * 0.6 + fatigue_factor * 0.4).clamp(0.0, 1.0)
109    }
110}
111
112// Extension trait for OperatorRank helper methods
113pub trait OperatorRankExt {
114    /// Convert rank to numeric score (0.0-1.0) for leadership scoring
115    fn to_score(self) -> f64;
116
117    /// Get human-readable rank name
118    fn name(self) -> &'static str;
119}
120
121impl OperatorRankExt for OperatorRank {
122    fn to_score(self) -> f64 {
123        match self {
124            Self::Unspecified => 0.0,
125            Self::E1 => 0.10,
126            Self::E2 => 0.15,
127            Self::E3 => 0.20,
128            Self::E4 => 0.30,
129            Self::E5 => 0.40,
130            Self::E6 => 0.50,
131            Self::E7 => 0.60, // Cell leader typical
132            Self::E8 => 0.70,
133            Self::E9 => 0.80,
134            Self::W1 => 0.70,
135            Self::W2 => 0.75,
136            Self::W3 => 0.80,
137            Self::W4 => 0.85,
138            Self::W5 => 0.90,
139            Self::O1 => 0.85,
140            Self::O2 => 0.90,
141            Self::O3 => 0.95, // Platoon leader
142            Self::O4 => 0.97,
143            Self::O5 => 0.98,
144            Self::O6 => 0.99,
145            Self::O7 => 0.995,
146            Self::O8 => 0.997,
147            Self::O9 => 0.999,
148            Self::O10 => 1.0,
149        }
150    }
151
152    fn name(self) -> &'static str {
153        match self {
154            Self::Unspecified => "Unspecified",
155            Self::E1 => "Private (E-1)",
156            Self::E2 => "Private (E-2)",
157            Self::E3 => "Private First Class",
158            Self::E4 => "Corporal/Specialist",
159            Self::E5 => "Sergeant",
160            Self::E6 => "Staff Sergeant",
161            Self::E7 => "Sergeant First Class",
162            Self::E8 => "Master Sergeant",
163            Self::E9 => "Sergeant Major",
164            Self::W1 => "Warrant Officer 1",
165            Self::W2 => "Chief Warrant Officer 2",
166            Self::W3 => "Chief Warrant Officer 3",
167            Self::W4 => "Chief Warrant Officer 4",
168            Self::W5 => "Chief Warrant Officer 5",
169            Self::O1 => "Second Lieutenant",
170            Self::O2 => "First Lieutenant",
171            Self::O3 => "Captain",
172            Self::O4 => "Major",
173            Self::O5 => "Lieutenant Colonel",
174            Self::O6 => "Colonel",
175            Self::O7 => "Brigadier General",
176            Self::O8 => "Major General",
177            Self::O9 => "Lieutenant General",
178            Self::O10 => "General",
179        }
180    }
181}
182
183// Extension trait for AuthorityLevel helper methods
184pub trait AuthorityLevelExt {
185    /// Convert authority to numeric score (0.0-1.0) for leadership scoring
186    fn to_score(self) -> f64;
187
188    /// Check if this authority level can override machine decisions
189    fn can_override(self) -> bool;
190
191    /// Check if this authority level requires human approval for actions
192    fn requires_approval(self) -> bool;
193}
194
195impl AuthorityLevelExt for AuthorityLevel {
196    fn to_score(self) -> f64 {
197        match self {
198            Self::Unspecified => 0.0,
199            Self::Observer => 0.1,
200            Self::Advisor => 0.3,
201            Self::Supervisor => 0.5,
202            Self::Commander => 0.8,
203        }
204    }
205
206    fn can_override(self) -> bool {
207        matches!(self, Self::Supervisor | Self::Commander)
208    }
209
210    fn requires_approval(self) -> bool {
211        matches!(self, Self::Commander)
212    }
213}
214
215// Extension trait for HumanMachinePair helper methods
216pub trait HumanMachinePairExt {
217    /// Create a new human-machine pair
218    fn new(operators: Vec<Operator>, platform_ids: Vec<String>, binding_type: BindingType) -> Self;
219
220    /// Create an autonomous (no human) binding
221    fn autonomous(platform_id: String) -> Self;
222
223    /// Create a one-to-one human-platform pair
224    fn one_to_one(operator: Operator, platform_id: String) -> Self;
225
226    /// Check if this is an autonomous platform (no operators)
227    fn is_autonomous(&self) -> bool;
228
229    /// Get the primary operator (highest rank)
230    fn primary_operator(&self) -> Option<&Operator>;
231
232    /// Get highest rank among operators
233    fn max_rank(&self) -> Option<OperatorRank>;
234
235    /// Get highest authority level among operators
236    fn max_authority(&self) -> Option<AuthorityLevel>;
237
238    /// Check if any operator is overloaded
239    fn has_overloaded_operator(&self, threshold: f32) -> bool;
240
241    /// Get average operator effectiveness across all operators
242    fn avg_effectiveness(&self) -> f32;
243}
244
245impl HumanMachinePairExt for HumanMachinePair {
246    fn new(operators: Vec<Operator>, platform_ids: Vec<String>, binding_type: BindingType) -> Self {
247        Self {
248            operators,
249            platform_ids,
250            binding_type: binding_type as i32,
251            bound_at: None,
252        }
253    }
254
255    fn autonomous(platform_id: String) -> Self {
256        Self {
257            operators: Vec::new(),
258            platform_ids: vec![platform_id],
259            binding_type: BindingType::Unspecified as i32,
260            bound_at: None,
261        }
262    }
263
264    fn one_to_one(operator: Operator, platform_id: String) -> Self {
265        Self::new(vec![operator], vec![platform_id], BindingType::OneToOne)
266    }
267
268    fn is_autonomous(&self) -> bool {
269        self.operators.is_empty()
270    }
271
272    fn primary_operator(&self) -> Option<&Operator> {
273        // Return highest-ranking operator
274        self.operators.iter().max_by(|a, b| a.rank.cmp(&b.rank))
275    }
276
277    fn max_rank(&self) -> Option<OperatorRank> {
278        self.operators
279            .iter()
280            .map(|op| OperatorRank::try_from(op.rank).ok())
281            .max()
282            .flatten()
283    }
284
285    fn max_authority(&self) -> Option<AuthorityLevel> {
286        self.operators
287            .iter()
288            .map(|op| AuthorityLevel::try_from(op.authority_level).ok())
289            .max()
290            .flatten()
291    }
292
293    fn has_overloaded_operator(&self, threshold: f32) -> bool {
294        self.operators.iter().any(|op| op.is_overloaded(threshold))
295    }
296
297    fn avg_effectiveness(&self) -> f32 {
298        if self.operators.is_empty() {
299            return 1.0; // Autonomous nodes are always "effective"
300        }
301
302        let sum: f32 = self.operators.iter().map(|op| op.effectiveness()).sum();
303        sum / self.operators.len() as f32
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_operator_creation() {
313        let op = Operator::new(
314            "op1".to_string(),
315            "John Doe".to_string(),
316            OperatorRank::E7,
317            AuthorityLevel::Commander,
318            "11B".to_string(),
319        );
320
321        assert_eq!(op.id, "op1");
322        assert_eq!(op.rank, OperatorRank::E7 as i32);
323        assert_eq!(op.cognitive_load(), 0.0);
324        assert_eq!(op.fatigue(), 0.0);
325    }
326
327    #[test]
328    fn test_operator_cognitive_load() {
329        let mut op = Operator::new(
330            "op1".to_string(),
331            "John".to_string(),
332            OperatorRank::E5,
333            AuthorityLevel::Supervisor,
334            "11B".to_string(),
335        );
336
337        op.update_cognitive_load(0.8);
338        assert_eq!(op.cognitive_load(), 0.8);
339        assert!(op.is_overloaded(0.7));
340        assert!(!op.is_overloaded(0.9));
341
342        // Test clamping
343        op.update_cognitive_load(1.5);
344        assert_eq!(op.cognitive_load(), 1.0);
345    }
346
347    #[test]
348    fn test_operator_effectiveness() {
349        let mut op = Operator::new(
350            "op1".to_string(),
351            "John".to_string(),
352            OperatorRank::E5,
353            AuthorityLevel::Supervisor,
354            "11B".to_string(),
355        );
356
357        // Fresh operator
358        assert_eq!(op.effectiveness(), 1.0);
359
360        // High cognitive load, low fatigue
361        op.update_cognitive_load(0.8);
362        op.update_fatigue(0.2);
363        let eff = op.effectiveness();
364        assert!(eff > 0.0 && eff < 1.0);
365
366        // High both
367        op.update_cognitive_load(0.9);
368        op.update_fatigue(0.9);
369        assert!(op.effectiveness() < 0.2);
370    }
371
372    #[test]
373    fn test_rank_ordering() {
374        assert!(OperatorRank::E7 > OperatorRank::E5);
375        assert!(OperatorRank::O3 > OperatorRank::E9);
376        assert!(OperatorRank::W5 > OperatorRank::W1);
377    }
378
379    #[test]
380    fn test_rank_to_score() {
381        assert_eq!(OperatorRank::E1.to_score(), 0.10);
382        assert_eq!(OperatorRank::E7.to_score(), 0.60);
383        assert_eq!(OperatorRank::O3.to_score(), 0.95);
384        assert_eq!(OperatorRank::O10.to_score(), 1.0);
385    }
386
387    #[test]
388    fn test_authority_level_ordering() {
389        assert!(AuthorityLevel::Commander > AuthorityLevel::Supervisor);
390        assert!(AuthorityLevel::Commander > AuthorityLevel::Observer);
391    }
392
393    #[test]
394    fn test_authority_can_override() {
395        assert!(!AuthorityLevel::Observer.can_override());
396        assert!(!AuthorityLevel::Advisor.can_override());
397        assert!(AuthorityLevel::Supervisor.can_override());
398        assert!(AuthorityLevel::Commander.can_override());
399    }
400
401    #[test]
402    fn test_human_machine_pair_autonomous() {
403        let pair = HumanMachinePair::autonomous("node_1".to_string());
404        assert!(pair.is_autonomous());
405        assert_eq!(pair.operators.len(), 0);
406        assert_eq!(pair.avg_effectiveness(), 1.0);
407    }
408
409    #[test]
410    fn test_human_machine_pair_one_to_one() {
411        let op = Operator::new(
412            "op1".to_string(),
413            "John".to_string(),
414            OperatorRank::E5,
415            AuthorityLevel::Commander,
416            "11B".to_string(),
417        );
418
419        let pair = HumanMachinePair::one_to_one(op, "node_1".to_string());
420
421        assert!(!pair.is_autonomous());
422        assert_eq!(pair.operators.len(), 1);
423        assert_eq!(pair.binding_type, BindingType::OneToOne as i32);
424        assert_eq!(pair.max_rank(), Some(OperatorRank::E5));
425    }
426
427    #[test]
428    fn test_human_machine_pair_primary_operator() {
429        let op1 = Operator::new(
430            "op1".to_string(),
431            "John".to_string(),
432            OperatorRank::E5,
433            AuthorityLevel::Supervisor,
434            "11B".to_string(),
435        );
436        let op2 = Operator::new(
437            "op2".to_string(),
438            "Jane".to_string(),
439            OperatorRank::E7,
440            AuthorityLevel::Commander,
441            "11B".to_string(),
442        );
443
444        let pair = HumanMachinePair::new(
445            vec![op1, op2],
446            vec!["node_1".to_string()],
447            BindingType::ManyToOne,
448        );
449
450        // Should return highest-ranking operator
451        let primary = pair.primary_operator().unwrap();
452        assert_eq!(primary.rank, OperatorRank::E7 as i32);
453        assert_eq!(primary.name, "Jane");
454    }
455
456    #[test]
457    fn test_human_machine_pair_max_authority() {
458        let op1 = Operator::new(
459            "op1".to_string(),
460            "John".to_string(),
461            OperatorRank::E7,
462            AuthorityLevel::Supervisor,
463            "11B".to_string(),
464        );
465        let op2 = Operator::new(
466            "op2".to_string(),
467            "Jane".to_string(),
468            OperatorRank::E5,
469            AuthorityLevel::Commander,
470            "11B".to_string(),
471        );
472
473        let pair = HumanMachinePair::new(
474            vec![op1, op2],
475            vec!["node_1".to_string()],
476            BindingType::ManyToOne,
477        );
478
479        assert_eq!(pair.max_authority(), Some(AuthorityLevel::Commander));
480    }
481
482    #[test]
483    fn test_human_machine_pair_overloaded_check() {
484        let mut op1 = Operator::new(
485            "op1".to_string(),
486            "John".to_string(),
487            OperatorRank::E5,
488            AuthorityLevel::Supervisor,
489            "11B".to_string(),
490        );
491        op1.update_cognitive_load(0.9);
492
493        let op2 = Operator::new(
494            "op2".to_string(),
495            "Jane".to_string(),
496            OperatorRank::E7,
497            AuthorityLevel::Commander,
498            "11B".to_string(),
499        );
500
501        let pair = HumanMachinePair::new(
502            vec![op1, op2],
503            vec!["node_1".to_string()],
504            BindingType::ManyToOne,
505        );
506
507        assert!(pair.has_overloaded_operator(0.8));
508        assert!(!pair.has_overloaded_operator(0.95));
509    }
510
511    #[test]
512    fn test_operator_cognitive_load_clamping() {
513        let mut op = Operator::new(
514            "op1".to_string(),
515            "Test".to_string(),
516            OperatorRank::E5,
517            AuthorityLevel::Supervisor,
518            "11B".to_string(),
519        );
520
521        // Test upper bound clamping
522        op.update_cognitive_load(2.0);
523        assert_eq!(op.cognitive_load(), 1.0);
524
525        // Test lower bound clamping
526        op.update_cognitive_load(-0.5);
527        assert_eq!(op.cognitive_load(), 0.0);
528
529        // Test normal value
530        op.update_cognitive_load(0.5);
531        assert_eq!(op.cognitive_load(), 0.5);
532    }
533
534    #[test]
535    fn test_operator_fatigue_clamping() {
536        let mut op = Operator::new(
537            "op1".to_string(),
538            "Test".to_string(),
539            OperatorRank::E5,
540            AuthorityLevel::Supervisor,
541            "11B".to_string(),
542        );
543
544        // Test upper bound clamping
545        op.update_fatigue(1.5);
546        assert_eq!(op.fatigue(), 1.0);
547
548        // Test lower bound clamping
549        op.update_fatigue(-0.3);
550        assert_eq!(op.fatigue(), 0.0);
551    }
552
553    #[test]
554    fn test_operator_is_overloaded_edge_cases() {
555        let mut op = Operator::new(
556            "op1".to_string(),
557            "Test".to_string(),
558            OperatorRank::E5,
559            AuthorityLevel::Supervisor,
560            "11B".to_string(),
561        );
562
563        op.update_cognitive_load(0.75);
564
565        // Test exact threshold
566        assert!(!op.is_overloaded(0.75));
567        assert!(!op.is_overloaded(0.76));
568        assert!(op.is_overloaded(0.74));
569        assert!(op.is_overloaded(0.0));
570    }
571
572    #[test]
573    fn test_operator_is_fatigued_edge_cases() {
574        let mut op = Operator::new(
575            "op1".to_string(),
576            "Test".to_string(),
577            OperatorRank::E5,
578            AuthorityLevel::Supervisor,
579            "11B".to_string(),
580        );
581
582        op.update_fatigue(0.6);
583
584        // Test exact threshold
585        assert!(!op.is_fatigued(0.6));
586        assert!(!op.is_fatigued(0.7));
587        assert!(op.is_fatigued(0.5));
588    }
589
590    #[test]
591    fn test_operator_effectiveness_edge_cases() {
592        let mut op = Operator::new(
593            "op1".to_string(),
594            "Test".to_string(),
595            OperatorRank::E5,
596            AuthorityLevel::Supervisor,
597            "11B".to_string(),
598        );
599
600        // Fully effective
601        assert_eq!(op.effectiveness(), 1.0);
602
603        // Completely overloaded and fatigued
604        op.update_cognitive_load(1.0);
605        op.update_fatigue(1.0);
606        assert_eq!(op.effectiveness(), 0.0);
607
608        // Only cognitive load affected
609        op.update_cognitive_load(1.0);
610        op.update_fatigue(0.0);
611        let eff = op.effectiveness();
612        assert!(eff > 0.0 && eff < 1.0);
613        assert_eq!(eff, 0.4); // 0% cognitive * 0.6 + 100% fatigue * 0.4 = 0.4
614    }
615
616    #[test]
617    fn test_operator_metadata_json_invalid() {
618        let mut op = Operator::new(
619            "op1".to_string(),
620            "Test".to_string(),
621            OperatorRank::E5,
622            AuthorityLevel::Supervisor,
623            "11B".to_string(),
624        );
625
626        // Set invalid JSON
627        op.metadata_json = "not valid json".to_string();
628
629        // Should return default values (0.0) when JSON is invalid
630        assert_eq!(op.cognitive_load(), 0.0);
631        assert_eq!(op.fatigue(), 0.0);
632    }
633
634    #[test]
635    fn test_rank_to_score_all_ranks() {
636        // Test that all ranks have scores in valid range
637        // Note: Scores are not strictly ascending across E/W/O categories
638        // because W (Warrant) and O (Officer) ranks can overlap with senior E ranks
639        let ranks = vec![
640            (OperatorRank::E1, 0.10),
641            (OperatorRank::E2, 0.15),
642            (OperatorRank::E3, 0.20),
643            (OperatorRank::E4, 0.30),
644            (OperatorRank::E5, 0.40),
645            (OperatorRank::E6, 0.50),
646            (OperatorRank::E7, 0.60),
647            (OperatorRank::E8, 0.70),
648            (OperatorRank::E9, 0.80),
649            (OperatorRank::W1, 0.70),
650            (OperatorRank::W2, 0.75),
651            (OperatorRank::W3, 0.80),
652            (OperatorRank::W4, 0.85),
653            (OperatorRank::W5, 0.90),
654            (OperatorRank::O1, 0.85),
655            (OperatorRank::O2, 0.90),
656            (OperatorRank::O3, 0.95),
657            (OperatorRank::O4, 0.97),
658            (OperatorRank::O5, 0.98),
659            (OperatorRank::O6, 0.99),
660            (OperatorRank::O7, 0.995),
661            (OperatorRank::O8, 0.997),
662            (OperatorRank::O9, 0.999),
663            (OperatorRank::O10, 1.0),
664        ];
665
666        for (rank, expected_score) in ranks {
667            let score = rank.to_score();
668            assert_eq!(
669                score, expected_score,
670                "Rank {:?} should have score {}",
671                rank, expected_score
672            );
673            assert!((0.0..=1.0).contains(&score));
674        }
675
676        // Verify that enlisted ranks are ascending
677        assert!(OperatorRank::E2.to_score() > OperatorRank::E1.to_score());
678        assert!(OperatorRank::E9.to_score() > OperatorRank::E8.to_score());
679
680        // Verify that warrant ranks are ascending
681        assert!(OperatorRank::W2.to_score() > OperatorRank::W1.to_score());
682        assert!(OperatorRank::W5.to_score() > OperatorRank::W4.to_score());
683
684        // Verify that officer ranks are ascending
685        assert!(OperatorRank::O2.to_score() > OperatorRank::O1.to_score());
686        assert!(OperatorRank::O10.to_score() > OperatorRank::O9.to_score());
687    }
688
689    #[test]
690    fn test_rank_name_all_ranks() {
691        // Ensure all ranks have names
692        let ranks = vec![
693            OperatorRank::Unspecified,
694            OperatorRank::E1,
695            OperatorRank::E5,
696            OperatorRank::E9,
697            OperatorRank::W1,
698            OperatorRank::W5,
699            OperatorRank::O1,
700            OperatorRank::O10,
701        ];
702
703        for rank in ranks {
704            let name = rank.name();
705            assert!(!name.is_empty());
706        }
707    }
708
709    #[test]
710    fn test_authority_level_to_score() {
711        assert_eq!(AuthorityLevel::Unspecified.to_score(), 0.0);
712        assert_eq!(AuthorityLevel::Observer.to_score(), 0.1);
713        assert_eq!(AuthorityLevel::Advisor.to_score(), 0.3);
714        assert_eq!(AuthorityLevel::Supervisor.to_score(), 0.5);
715        assert_eq!(AuthorityLevel::Commander.to_score(), 0.8);
716
717        // Verify ordering
718        assert!(AuthorityLevel::Commander.to_score() > AuthorityLevel::Supervisor.to_score());
719        assert!(AuthorityLevel::Supervisor.to_score() > AuthorityLevel::Advisor.to_score());
720        assert!(AuthorityLevel::Advisor.to_score() > AuthorityLevel::Observer.to_score());
721    }
722
723    #[test]
724    fn test_authority_requires_approval() {
725        assert!(!AuthorityLevel::Unspecified.requires_approval());
726        assert!(!AuthorityLevel::Observer.requires_approval());
727        assert!(!AuthorityLevel::Advisor.requires_approval());
728        assert!(!AuthorityLevel::Supervisor.requires_approval());
729        assert!(AuthorityLevel::Commander.requires_approval());
730    }
731
732    #[test]
733    fn test_human_machine_pair_avg_effectiveness_multiple() {
734        let mut op1 = Operator::new(
735            "op1".to_string(),
736            "Op1".to_string(),
737            OperatorRank::E5,
738            AuthorityLevel::Supervisor,
739            "11B".to_string(),
740        );
741        op1.update_cognitive_load(0.2);
742        op1.update_fatigue(0.2);
743
744        let mut op2 = Operator::new(
745            "op2".to_string(),
746            "Op2".to_string(),
747            OperatorRank::E6,
748            AuthorityLevel::Supervisor,
749            "11B".to_string(),
750        );
751        op2.update_cognitive_load(0.8);
752        op2.update_fatigue(0.8);
753
754        let pair = HumanMachinePair::new(
755            vec![op1.clone(), op2.clone()],
756            vec!["node_1".to_string()],
757            BindingType::ManyToOne,
758        );
759
760        let avg = pair.avg_effectiveness();
761        let expected = (op1.effectiveness() + op2.effectiveness()) / 2.0;
762        assert_eq!(avg, expected);
763    }
764
765    #[test]
766    fn test_human_machine_pair_multiple_platforms() {
767        let op = Operator::new(
768            "op1".to_string(),
769            "Operator".to_string(),
770            OperatorRank::E6,
771            AuthorityLevel::Supervisor,
772            "11B".to_string(),
773        );
774
775        let platform_ids = vec![
776            "p1".to_string(),
777            "p2".to_string(),
778            "p3".to_string(),
779            "p4".to_string(),
780            "p5".to_string(),
781        ];
782
783        let pair = HumanMachinePair::new(vec![op], platform_ids.clone(), BindingType::OneToMany);
784
785        assert_eq!(pair.platform_ids.len(), 5);
786        assert_eq!(pair.operators.len(), 1);
787        assert!(!pair.is_autonomous());
788    }
789
790    #[test]
791    fn test_human_machine_pair_max_rank_and_authority_mismatch() {
792        // Lower rank but higher authority
793        let op1 = Operator::new(
794            "op1".to_string(),
795            "Junior Commander".to_string(),
796            OperatorRank::E4,
797            AuthorityLevel::Commander,
798            "11B".to_string(),
799        );
800
801        // Higher rank but lower authority
802        let op2 = Operator::new(
803            "op2".to_string(),
804            "Senior Advisor".to_string(),
805            OperatorRank::E8,
806            AuthorityLevel::Advisor,
807            "11B".to_string(),
808        );
809
810        let pair = HumanMachinePair::new(
811            vec![op1, op2],
812            vec!["node_1".to_string()],
813            BindingType::ManyToOne,
814        );
815
816        // Primary operator should be highest rank (E8)
817        let primary = pair.primary_operator().unwrap();
818        assert_eq!(primary.rank, OperatorRank::E8 as i32);
819
820        // But max authority should be Commander
821        assert_eq!(pair.max_authority(), Some(AuthorityLevel::Commander));
822
823        // Max rank should be E8
824        assert_eq!(pair.max_rank(), Some(OperatorRank::E8));
825    }
826
827    #[test]
828    fn test_human_machine_pair_binding_types() {
829        let op = Operator::new(
830            "op1".to_string(),
831            "Operator".to_string(),
832            OperatorRank::E5,
833            AuthorityLevel::Supervisor,
834            "11B".to_string(),
835        );
836
837        for binding_type in [
838            BindingType::Unspecified,
839            BindingType::OneToOne,
840            BindingType::OneToMany,
841            BindingType::ManyToOne,
842            BindingType::ManyToMany,
843        ] {
844            let pair =
845                HumanMachinePair::new(vec![op.clone()], vec!["node_1".to_string()], binding_type);
846            assert_eq!(pair.binding_type, binding_type as i32);
847        }
848    }
849}