swarm_engine_core/events/
action.rs1use std::time::Duration;
7
8use crate::types::{LoraConfig, WorkerId};
9
10#[derive(Debug, Clone)]
15pub struct ActionEvent {
16 pub id: ActionEventId,
18 pub tick: u64,
20 pub worker_id: WorkerId,
22 pub action: String,
24 pub target: Option<String>,
26 pub result: ActionEventResult,
28 pub duration: Duration,
30 pub context: ActionContext,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct ActionEventId(pub u64);
37
38impl ActionEventId {
39 pub fn new(tick: u64, worker_id: WorkerId, sequence: u32) -> Self {
41 let id = (tick << 24) | ((worker_id.0 as u64) << 8) | (sequence as u64 & 0xFF);
43 Self(id)
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct ActionEventResult {
50 pub success: bool,
52 pub output: Option<String>,
54 pub error: Option<String>,
56 pub discoveries: u32,
58 pub kpi_contribution: Option<f64>,
63}
64
65impl ActionEventResult {
66 pub fn success() -> Self {
67 Self {
68 success: true,
69 output: None,
70 error: None,
71 discoveries: 0,
72 kpi_contribution: None,
73 }
74 }
75
76 pub fn success_with_output(output: impl Into<String>) -> Self {
77 Self {
78 success: true,
79 output: Some(output.into()),
80 error: None,
81 discoveries: 0,
82 kpi_contribution: None,
83 }
84 }
85
86 pub fn failure(error: impl Into<String>) -> Self {
87 Self {
88 success: false,
89 output: None,
90 error: Some(error.into()),
91 discoveries: 0,
92 kpi_contribution: None,
93 }
94 }
95
96 pub fn with_discoveries(mut self, count: u32) -> Self {
97 self.discoveries = count;
98 self
99 }
100
101 pub fn with_kpi(mut self, contribution: f64) -> Self {
108 self.kpi_contribution = Some(contribution);
109 self
110 }
111}
112
113#[derive(Debug, Clone, Default)]
118pub struct ActionContext {
119 pub selection_logic: Option<String>,
121 pub exploration_node_id: Option<u64>,
123 pub from_guidance: bool,
125 pub previous_action: Option<String>,
127 pub lora: Option<LoraConfig>,
129 pub metadata: std::collections::HashMap<String, String>,
131}
132
133impl ActionContext {
134 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn with_selection_logic(mut self, logic: impl Into<String>) -> Self {
139 self.selection_logic = Some(logic.into());
140 self
141 }
142
143 pub fn with_exploration_node(mut self, node_id: u64) -> Self {
144 self.exploration_node_id = Some(node_id);
145 self
146 }
147
148 pub fn with_guidance(mut self) -> Self {
149 self.from_guidance = true;
150 self
151 }
152
153 pub fn with_previous_action(mut self, action: impl Into<String>) -> Self {
154 self.previous_action = Some(action.into());
155 self
156 }
157
158 pub fn with_lora(mut self, lora: LoraConfig) -> Self {
159 self.lora = Some(lora);
160 self
161 }
162
163 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
164 self.metadata.insert(key.into(), value.into());
165 self
166 }
167}
168
169pub struct ActionEventBuilder {
171 tick: u64,
172 worker_id: WorkerId,
173 sequence: u32,
174 action: String,
175 target: Option<String>,
176 result: ActionEventResult,
177 duration: Duration,
178 context: ActionContext,
179}
180
181impl ActionEventBuilder {
182 pub fn new(tick: u64, worker_id: WorkerId, action: impl Into<String>) -> Self {
183 Self {
184 tick,
185 worker_id,
186 sequence: 0,
187 action: action.into(),
188 target: None,
189 result: ActionEventResult::success(),
190 duration: Duration::ZERO,
191 context: ActionContext::default(),
192 }
193 }
194
195 pub fn sequence(mut self, seq: u32) -> Self {
196 self.sequence = seq;
197 self
198 }
199
200 pub fn target(mut self, target: impl Into<String>) -> Self {
201 self.target = Some(target.into());
202 self
203 }
204
205 pub fn result(mut self, result: ActionEventResult) -> Self {
206 self.result = result;
207 self
208 }
209
210 pub fn duration(mut self, duration: Duration) -> Self {
211 self.duration = duration;
212 self
213 }
214
215 pub fn context(mut self, context: ActionContext) -> Self {
216 self.context = context;
217 self
218 }
219
220 pub fn build(self) -> ActionEvent {
221 ActionEvent {
222 id: ActionEventId::new(self.tick, self.worker_id, self.sequence),
223 tick: self.tick,
224 worker_id: self.worker_id,
225 action: self.action,
226 target: self.target,
227 result: self.result,
228 duration: self.duration,
229 context: self.context,
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_action_event_id_uniqueness() {
240 let id1 = ActionEventId::new(100, WorkerId(0), 0);
241 let id2 = ActionEventId::new(100, WorkerId(0), 1);
242 let id3 = ActionEventId::new(100, WorkerId(1), 0);
243 let id4 = ActionEventId::new(101, WorkerId(0), 0);
244
245 assert_ne!(id1, id2);
246 assert_ne!(id1, id3);
247 assert_ne!(id1, id4);
248 }
249
250 #[test]
251 fn test_action_event_builder() {
252 let event = ActionEventBuilder::new(10, WorkerId(1), "CheckStatus")
253 .target("user-service")
254 .result(ActionEventResult::success_with_output("Service is running"))
255 .duration(Duration::from_millis(50))
256 .context(
257 ActionContext::new()
258 .with_selection_logic("UCB1")
259 .with_guidance(),
260 )
261 .build();
262
263 assert_eq!(event.tick, 10);
264 assert_eq!(event.worker_id, WorkerId(1));
265 assert_eq!(event.action, "CheckStatus");
266 assert_eq!(event.target, Some("user-service".to_string()));
267 assert!(event.result.success);
268 assert_eq!(event.duration, Duration::from_millis(50));
269 assert_eq!(event.context.selection_logic, Some("UCB1".to_string()));
270 assert!(event.context.from_guidance);
271 }
272}