1use crate::salience::Salience;
4use serde::{Deserialize, Serialize};
5use std::time::Instant;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum CoreId {
10 Citta,
12 Dream,
14 BrainWave,
16 Autonomous,
18 Dispatch,
20 Reflex,
22 SelfModel,
24 Drive,
26 Homeostasis,
28 Sensor,
30 Custom(u16),
32}
33
34impl CoreId {
35 #[must_use]
37 pub const fn name(&self) -> &str {
38 match self {
39 Self::Citta => "citta",
40 Self::Dream => "dream",
41 Self::BrainWave => "brain_wave",
42 Self::Autonomous => "autonomous",
43 Self::Dispatch => "dispatch",
44 Self::Reflex => "reflex",
45 Self::SelfModel => "self_model",
46 Self::Drive => "drive",
47 Self::Homeostasis => "homeostasis",
48 Self::Sensor => "sensor",
49 Self::Custom(_) => "custom",
50 }
51 }
52}
53
54impl std::fmt::Display for CoreId {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::Custom(id) => write!(f, "custom_{id}"),
58 _ => write!(f, "{}", self.name()),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65pub enum EventType {
66 Error,
68 Reward,
70 AttentionRequest,
72 NovelDetection,
74 ThresholdCrossing,
76 DriveUpdate,
78 SafetyAlert,
80}
81
82impl EventType {
83 #[must_use]
85 pub const fn name(&self) -> &'static str {
86 match self {
87 Self::Error => "error",
88 Self::Reward => "reward",
89 Self::AttentionRequest => "attention_request",
90 Self::NovelDetection => "novel_detection",
91 Self::ThresholdCrossing => "threshold_crossing",
92 Self::DriveUpdate => "drive_update",
93 Self::SafetyAlert => "safety_alert",
94 }
95 }
96}
97
98impl std::fmt::Display for EventType {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 write!(f, "{}", self.name())
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct WorkspaceEvent {
111 pub core: CoreId,
113 pub event_type: EventType,
115 pub salience: Salience,
117 pub payload: serde_json::Value,
119 #[serde(skip, default = "Instant::now")]
121 pub timestamp: Instant,
122}
123
124impl WorkspaceEvent {
125 #[must_use]
127 pub fn new(
128 core: CoreId,
129 event_type: EventType,
130 salience: Salience,
131 payload: serde_json::Value,
132 ) -> Self {
133 Self {
134 core,
135 event_type,
136 salience,
137 payload,
138 timestamp: Instant::now(),
139 }
140 }
141
142 #[must_use]
144 pub fn with_default_urgency(
145 core: CoreId,
146 event_type: EventType,
147 novelty: f32,
148 confidence: f32,
149 payload: serde_json::Value,
150 ) -> Self {
151 let urgency = crate::salience::default_urgency(&event_type);
152 Self::new(
153 core,
154 event_type,
155 Salience::new(urgency, novelty, confidence),
156 payload,
157 )
158 }
159
160 #[must_use]
162 pub fn composite_salience(&self) -> f32 {
163 self.salience.composite()
164 }
165
166 #[must_use]
168 pub fn should_preempt(&self) -> bool {
169 self.salience.is_high_salience()
170 }
171
172 #[must_use]
174 pub fn age(&self) -> std::time::Duration {
175 self.timestamp.elapsed()
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn core_id_names() {
185 assert_eq!(CoreId::Citta.name(), "citta");
186 assert_eq!(CoreId::Dream.name(), "dream");
187 assert_eq!(CoreId::Reflex.name(), "reflex");
188 assert_eq!(CoreId::Custom(42).name(), "custom");
189 }
190
191 #[test]
192 fn core_id_display_custom() {
193 assert_eq!(format!("{}", CoreId::Custom(7)), "custom_7");
194 assert_eq!(format!("{}", CoreId::Citta), "citta");
195 }
196
197 #[test]
198 fn event_type_names() {
199 assert_eq!(EventType::Error.name(), "error");
200 assert_eq!(EventType::SafetyAlert.name(), "safety_alert");
201 assert_eq!(EventType::NovelDetection.name(), "novel_detection");
202 }
203
204 #[test]
205 fn event_new() {
206 let event = WorkspaceEvent::new(
207 CoreId::Reflex,
208 EventType::SafetyAlert,
209 Salience::new(1.0, 0.8, 0.9),
210 serde_json::json!({"sensor": "imu_1", "value": 42.0}),
211 );
212 assert_eq!(event.core, CoreId::Reflex);
213 assert_eq!(event.event_type, EventType::SafetyAlert);
214 assert!((event.composite_salience() - 0.72).abs() < 0.001);
215 }
216
217 #[test]
218 fn event_with_default_urgency() {
219 let event = WorkspaceEvent::with_default_urgency(
220 CoreId::Homeostasis,
221 EventType::ThresholdCrossing,
222 0.5,
223 0.9,
224 serde_json::json!({"metric": "cpu", "value": 95.0}),
225 );
226 assert!((event.salience.urgency - 0.8).abs() < 0.001);
228 assert!((event.salience.novelty - 0.5).abs() < 0.001);
229 assert!((event.salience.confidence - 0.9).abs() < 0.001);
230 }
231
232 #[test]
233 fn event_should_preempt() {
234 let high = WorkspaceEvent::new(
235 CoreId::Reflex,
236 EventType::SafetyAlert,
237 Salience::new(0.95, 0.95, 0.95),
238 serde_json::json!({}),
239 );
240 assert!(high.should_preempt());
241
242 let low = WorkspaceEvent::new(
243 CoreId::Drive,
244 EventType::DriveUpdate,
245 Salience::new(0.2, 0.3, 0.5),
246 serde_json::json!({}),
247 );
248 assert!(!low.should_preempt());
249 }
250
251 #[test]
252 fn event_age_grows() {
253 let event = WorkspaceEvent::new(
254 CoreId::Citta,
255 EventType::AttentionRequest,
256 Salience::new(0.5, 0.5, 0.5),
257 serde_json::json!({}),
258 );
259 std::thread::sleep(std::time::Duration::from_millis(10));
260 assert!(event.age().as_millis() >= 10);
261 }
262}