use crate::salience::Salience;
use serde::{Deserialize, Serialize};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CoreId {
Citta,
Dream,
BrainWave,
Autonomous,
Dispatch,
Reflex,
SelfModel,
Drive,
Homeostasis,
Sensor,
Custom(u16),
}
impl CoreId {
#[must_use]
pub const fn name(&self) -> &str {
match self {
Self::Citta => "citta",
Self::Dream => "dream",
Self::BrainWave => "brain_wave",
Self::Autonomous => "autonomous",
Self::Dispatch => "dispatch",
Self::Reflex => "reflex",
Self::SelfModel => "self_model",
Self::Drive => "drive",
Self::Homeostasis => "homeostasis",
Self::Sensor => "sensor",
Self::Custom(_) => "custom",
}
}
}
impl std::fmt::Display for CoreId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(id) => write!(f, "custom_{id}"),
_ => write!(f, "{}", self.name()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EventType {
Error,
Reward,
AttentionRequest,
NovelDetection,
ThresholdCrossing,
DriveUpdate,
SafetyAlert,
}
impl EventType {
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Error => "error",
Self::Reward => "reward",
Self::AttentionRequest => "attention_request",
Self::NovelDetection => "novel_detection",
Self::ThresholdCrossing => "threshold_crossing",
Self::DriveUpdate => "drive_update",
Self::SafetyAlert => "safety_alert",
}
}
}
impl std::fmt::Display for EventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceEvent {
pub core: CoreId,
pub event_type: EventType,
pub salience: Salience,
pub payload: serde_json::Value,
#[serde(skip, default = "Instant::now")]
pub timestamp: Instant,
}
impl WorkspaceEvent {
#[must_use]
pub fn new(
core: CoreId,
event_type: EventType,
salience: Salience,
payload: serde_json::Value,
) -> Self {
Self {
core,
event_type,
salience,
payload,
timestamp: Instant::now(),
}
}
#[must_use]
pub fn with_default_urgency(
core: CoreId,
event_type: EventType,
novelty: f32,
confidence: f32,
payload: serde_json::Value,
) -> Self {
let urgency = crate::salience::default_urgency(&event_type);
Self::new(
core,
event_type,
Salience::new(urgency, novelty, confidence),
payload,
)
}
#[must_use]
pub fn composite_salience(&self) -> f32 {
self.salience.composite()
}
#[must_use]
pub fn should_preempt(&self) -> bool {
self.salience.is_high_salience()
}
#[must_use]
pub fn age(&self) -> std::time::Duration {
self.timestamp.elapsed()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn core_id_names() {
assert_eq!(CoreId::Citta.name(), "citta");
assert_eq!(CoreId::Dream.name(), "dream");
assert_eq!(CoreId::Reflex.name(), "reflex");
assert_eq!(CoreId::Custom(42).name(), "custom");
}
#[test]
fn core_id_display_custom() {
assert_eq!(format!("{}", CoreId::Custom(7)), "custom_7");
assert_eq!(format!("{}", CoreId::Citta), "citta");
}
#[test]
fn event_type_names() {
assert_eq!(EventType::Error.name(), "error");
assert_eq!(EventType::SafetyAlert.name(), "safety_alert");
assert_eq!(EventType::NovelDetection.name(), "novel_detection");
}
#[test]
fn event_new() {
let event = WorkspaceEvent::new(
CoreId::Reflex,
EventType::SafetyAlert,
Salience::new(1.0, 0.8, 0.9),
serde_json::json!({"sensor": "imu_1", "value": 42.0}),
);
assert_eq!(event.core, CoreId::Reflex);
assert_eq!(event.event_type, EventType::SafetyAlert);
assert!((event.composite_salience() - 0.72).abs() < 0.001);
}
#[test]
fn event_with_default_urgency() {
let event = WorkspaceEvent::with_default_urgency(
CoreId::Homeostasis,
EventType::ThresholdCrossing,
0.5,
0.9,
serde_json::json!({"metric": "cpu", "value": 95.0}),
);
assert!((event.salience.urgency - 0.8).abs() < 0.001);
assert!((event.salience.novelty - 0.5).abs() < 0.001);
assert!((event.salience.confidence - 0.9).abs() < 0.001);
}
#[test]
fn event_should_preempt() {
let high = WorkspaceEvent::new(
CoreId::Reflex,
EventType::SafetyAlert,
Salience::new(0.95, 0.95, 0.95),
serde_json::json!({}),
);
assert!(high.should_preempt());
let low = WorkspaceEvent::new(
CoreId::Drive,
EventType::DriveUpdate,
Salience::new(0.2, 0.3, 0.5),
serde_json::json!({}),
);
assert!(!low.should_preempt());
}
#[test]
fn event_age_grows() {
let event = WorkspaceEvent::new(
CoreId::Citta,
EventType::AttentionRequest,
Salience::new(0.5, 0.5, 0.5),
serde_json::json!({}),
);
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(event.age().as_millis() >= 10);
}
}