phytrace_sdk/models/domains/
coordination.rs

1//! Coordination Domain - Fleet coordination and traffic management.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use validator::Validate;
6
7use super::common::Position2D;
8use crate::models::enums::{FleetRole, FormationType, TrafficPriority};
9
10/// Coordination domain containing fleet and traffic information.
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct CoordinationDomain {
13    /// Fleet role
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub fleet_role: Option<FleetRole>,
16
17    /// Formation information
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub formation: Option<Formation>,
20
21    /// Nearby robots
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub neighbors: Option<Vec<NeighborRobot>>,
24
25    /// Swarm information
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub swarm: Option<SwarmInfo>,
28
29    /// Traffic management
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub traffic: Option<TrafficManagement>,
32}
33
34/// Formation information.
35#[derive(Debug, Clone, Default, Serialize, Deserialize)]
36pub struct Formation {
37    /// Formation ID
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub formation_id: Option<String>,
40
41    /// Formation type
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub formation_type: Option<FormationType>,
44
45    /// Position in formation
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub position_index: Option<u32>,
48
49    /// Total formation size
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub formation_size: Option<u32>,
52
53    /// Distance to formation position (m)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub position_error_m: Option<f64>,
56
57    /// Leader ID (if follower)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub leader_id: Option<String>,
60
61    /// Whether in formation
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub is_in_formation: Option<bool>,
64}
65
66/// Nearby robot information.
67#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
68pub struct NeighborRobot {
69    /// Robot ID
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub robot_id: Option<String>,
72
73    /// Distance (m)
74    #[serde(skip_serializing_if = "Option::is_none")]
75    #[validate(range(min = 0.0))]
76    pub distance_m: Option<f64>,
77
78    /// Bearing (degrees)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub bearing_deg: Option<f64>,
81
82    /// Velocity (m/s)
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub velocity_mps: Option<f64>,
85
86    /// Heading (degrees)
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub heading_deg: Option<f64>,
89
90    /// Last communication time
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub last_comm: Option<DateTime<Utc>>,
93
94    /// Communication latency (ms)
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub comm_latency_ms: Option<f64>,
97
98    /// Whether potential collision
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub collision_risk: Option<bool>,
101}
102
103/// Swarm information.
104#[derive(Debug, Clone, Default, Serialize, Deserialize)]
105pub struct SwarmInfo {
106    /// Swarm ID
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub swarm_id: Option<String>,
109
110    /// Swarm size
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub swarm_size: Option<u32>,
113
114    /// Swarm state
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub state: Option<String>,
117
118    /// Consensus value (task-dependent)
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub consensus_value: Option<f64>,
121
122    /// Swarm centroid
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub centroid: Option<Position2D>,
125}
126
127/// Traffic management information.
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129pub struct TrafficManagement {
130    /// Current priority
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub priority: Option<TrafficPriority>,
133
134    /// Whether at intersection
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub at_intersection: Option<bool>,
137
138    /// Intersection ID
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub intersection_id: Option<String>,
141
142    /// Whether has right of way
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub has_right_of_way: Option<bool>,
145
146    /// Waiting for robot IDs
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub waiting_for: Option<Vec<String>>,
149
150    /// Wait time (seconds)
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub wait_time_sec: Option<f64>,
153
154    /// Reserved path segments
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub reserved_segments: Option<Vec<String>>,
157
158    /// Deadlock detected
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub deadlock_detected: Option<bool>,
161}
162
163impl CoordinationDomain {
164    /// Create with fleet role.
165    pub fn new(role: FleetRole) -> Self {
166        Self {
167            fleet_role: Some(role),
168            ..Default::default()
169        }
170    }
171
172    /// Add a neighbor robot.
173    pub fn with_neighbor(mut self, neighbor: NeighborRobot) -> Self {
174        let neighbors = self.neighbors.get_or_insert_with(Vec::new);
175        neighbors.push(neighbor);
176        self
177    }
178
179    /// Add formation info.
180    pub fn with_formation(mut self, formation: Formation) -> Self {
181        self.formation = Some(formation);
182        self
183    }
184
185    /// Add traffic management.
186    pub fn with_traffic(mut self, traffic: TrafficManagement) -> Self {
187        self.traffic = Some(traffic);
188        self
189    }
190}
191
192impl NeighborRobot {
193    /// Create a neighbor robot.
194    pub fn new(robot_id: impl Into<String>, distance_m: f64) -> Self {
195        Self {
196            robot_id: Some(robot_id.into()),
197            distance_m: Some(distance_m),
198            last_comm: Some(Utc::now()),
199            ..Default::default()
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_coordination_domain() {
210        let coord = CoordinationDomain::new(FleetRole::Follower)
211            .with_neighbor(NeighborRobot::new("robot-002", 3.5));
212
213        assert_eq!(coord.fleet_role, Some(FleetRole::Follower));
214        assert_eq!(coord.neighbors.unwrap().len(), 1);
215    }
216}