use crate::types::{Confidence, Timestamp, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ObservationType {
Sensor(String),
Network(String),
UserInput(String),
StateChange(String),
Timer(String),
Alert(String),
Custom(String),
}
impl ObservationType {
pub fn sensor(name: &str) -> Self {
ObservationType::Sensor(name.to_string())
}
pub fn network(event: &str) -> Self {
ObservationType::Network(event.to_string())
}
pub fn alert(msg: &str) -> Self {
ObservationType::Alert(msg.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Observation {
pub obs_type: ObservationType,
pub value: Value,
pub timestamp: Timestamp,
pub confidence: Confidence,
pub metadata: HashMap<String, Value>,
}
impl Observation {
pub fn new(obs_type: ObservationType, value: impl Into<Value>) -> Self {
Self {
obs_type,
value: value.into(),
timestamp: Timestamp::now(),
confidence: Confidence::default(),
metadata: HashMap::new(),
}
}
pub fn sensor(name: &str, value: impl Into<Value>) -> Self {
Self::new(ObservationType::sensor(name), value)
}
pub fn alert(message: &str) -> Self {
Self::new(
ObservationType::alert(message),
Value::String(message.to_string()),
)
}
pub fn state_change(state_name: &str, new_value: impl Into<Value>) -> Self {
Self::new(
ObservationType::StateChange(state_name.to_string()),
new_value,
)
}
pub fn event(event_name: &str) -> Self {
Self::new(
ObservationType::Custom(event_name.to_string()),
Value::String(event_name.to_string()),
)
}
pub fn error(error_type: &str, message: &str) -> Self {
Self::new(
ObservationType::Alert(format!("error:{}", error_type)),
Value::String(message.to_string()),
)
}
pub fn network(event: &str, data: impl Into<Value>) -> Self {
Self::new(ObservationType::network(event), data)
}
pub fn timer(timer_name: &str) -> Self {
Self::new(
ObservationType::Timer(timer_name.to_string()),
Value::String(timer_name.to_string()),
)
}
pub fn with_confidence(mut self, confidence: f32) -> Self {
self.confidence = Confidence::new(confidence);
self
}
pub fn with_metadata(mut self, key: &str, value: impl Into<Value>) -> Self {
self.metadata.insert(key.to_string(), value.into());
self
}
pub fn age_secs(&self) -> u64 {
self.timestamp.age_secs()
}
pub fn is_recent(&self, threshold_secs: u64) -> bool {
self.age_secs() <= threshold_secs
}
}
pub trait Sensor {
fn name(&self) -> &str;
fn read(&self) -> Option<Observation>;
fn is_available(&self) -> bool {
true
}
}
pub struct ValueSensor {
name: String,
current_value: Option<Value>,
}
impl ValueSensor {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
current_value: None,
}
}
pub fn set_value(&mut self, value: impl Into<Value>) {
self.current_value = Some(value.into());
}
pub fn clear(&mut self) {
self.current_value = None;
}
}
impl Sensor for ValueSensor {
fn name(&self) -> &str {
&self.name
}
fn read(&self) -> Option<Observation> {
self.current_value
.as_ref()
.map(|v| Observation::sensor(&self.name, v.clone()))
}
fn is_available(&self) -> bool {
self.current_value.is_some()
}
}
pub struct ObservationBuffer {
observations: Vec<Observation>,
max_size: usize,
max_age_secs: u64,
}
impl ObservationBuffer {
pub fn new(max_size: usize) -> Self {
Self {
observations: Vec::with_capacity(max_size),
max_size,
max_age_secs: 300, }
}
pub fn with_max_age(mut self, secs: u64) -> Self {
self.max_age_secs = secs;
self
}
pub fn push(&mut self, obs: Observation) {
self.observations.retain(|o| o.is_recent(self.max_age_secs));
if self.observations.len() >= self.max_size {
self.observations.remove(0);
}
self.observations.push(obs);
}
pub fn get_recent(&self, count: usize) -> Vec<&Observation> {
self.observations.iter().rev().take(count).collect()
}
pub fn get_by_type(&self, obs_type: &ObservationType) -> Vec<&Observation> {
self.observations
.iter()
.filter(|o| &o.obs_type == obs_type)
.collect()
}
pub fn latest(&self) -> Option<&Observation> {
self.observations.last()
}
pub fn clear(&mut self) {
self.observations.clear();
}
pub fn len(&self) -> usize {
self.observations.len()
}
pub fn is_empty(&self) -> bool {
self.observations.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_observation_creation() {
let obs = Observation::sensor("temperature", 23.5);
assert!(matches!(obs.obs_type, ObservationType::Sensor(_)));
}
#[test]
fn test_observation_buffer() {
let mut buffer = ObservationBuffer::new(10);
buffer.push(Observation::sensor("temp", 20.0));
buffer.push(Observation::sensor("temp", 21.0));
assert_eq!(buffer.len(), 2);
assert!(buffer.latest().is_some());
}
#[test]
fn test_value_sensor() {
let mut sensor = ValueSensor::new("test");
assert!(!sensor.is_available());
sensor.set_value(42.0);
assert!(sensor.is_available());
let obs = sensor.read().unwrap();
assert_eq!(obs.value.as_f64().unwrap(), 42.0);
}
#[test]
fn test_observation_type_sensor() {
let obs_type = ObservationType::sensor("temperature");
assert!(matches!(obs_type, ObservationType::Sensor(s) if s == "temperature"));
}
#[test]
fn test_observation_type_network() {
let obs_type = ObservationType::network("peer_connected");
assert!(matches!(obs_type, ObservationType::Network(s) if s == "peer_connected"));
}
#[test]
fn test_observation_type_alert() {
let obs_type = ObservationType::alert("Critical error");
assert!(matches!(obs_type, ObservationType::Alert(s) if s == "Critical error"));
}
#[test]
fn test_observation_type_clone() {
let obs_type = ObservationType::sensor("temp");
let cloned = obs_type.clone();
assert_eq!(obs_type, cloned);
}
#[test]
fn test_observation_type_debug() {
let obs_type = ObservationType::sensor("temp");
let debug_str = format!("{:?}", obs_type);
assert!(debug_str.contains("Sensor"));
assert!(debug_str.contains("temp"));
}
#[test]
fn test_observation_type_serialize() {
let obs_type = ObservationType::sensor("temp");
let json = serde_json::to_string(&obs_type).unwrap();
assert!(json.contains("Sensor"));
let parsed: ObservationType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, obs_type);
}
#[test]
fn test_observation_type_eq_hash() {
use std::collections::HashSet;
let obs1 = ObservationType::sensor("temp");
let obs2 = ObservationType::sensor("temp");
let obs3 = ObservationType::network("peer");
assert_eq!(obs1, obs2);
assert_ne!(obs1, obs3);
let mut set = HashSet::new();
set.insert(obs1.clone());
set.insert(obs2.clone());
assert_eq!(set.len(), 1);
}
#[test]
fn test_observation_type_all_variants() {
let types = vec![
ObservationType::Sensor("s".into()),
ObservationType::Network("n".into()),
ObservationType::UserInput("u".into()),
ObservationType::StateChange("sc".into()),
ObservationType::Timer("t".into()),
ObservationType::Alert("a".into()),
ObservationType::Custom("c".into()),
];
for t in types {
let _ = format!("{:?}", t);
}
}
#[test]
fn test_observation_new() {
let obs = Observation::new(ObservationType::sensor("pressure"), 1013.25);
assert!(matches!(obs.obs_type, ObservationType::Sensor(_)));
assert_eq!(obs.value.as_f64().unwrap(), 1013.25);
}
#[test]
fn test_observation_alert() {
let obs = Observation::alert("Temperature threshold exceeded");
assert!(matches!(obs.obs_type, ObservationType::Alert(_)));
}
#[test]
fn test_observation_state_change() {
let obs = Observation::state_change("system_mode", "active");
assert!(matches!(obs.obs_type, ObservationType::StateChange(_)));
}
#[test]
fn test_observation_event() {
let obs = Observation::event("user_login");
assert!(matches!(obs.obs_type, ObservationType::Custom(_)));
}
#[test]
fn test_observation_error() {
let obs = Observation::error("network", "Connection timeout");
assert!(matches!(obs.obs_type, ObservationType::Alert(_)));
}
#[test]
fn test_observation_network() {
let obs = Observation::network("peer_connected", "peer_123");
assert!(matches!(obs.obs_type, ObservationType::Network(_)));
}
#[test]
fn test_observation_timer() {
let obs = Observation::timer("hourly_check");
assert!(matches!(obs.obs_type, ObservationType::Timer(_)));
}
#[test]
fn test_observation_with_confidence() {
let obs = Observation::sensor("noisy_sensor", 42.0).with_confidence(0.7);
assert!((obs.confidence.value() - 0.7).abs() < 0.001);
}
#[test]
fn test_observation_with_metadata() {
let obs = Observation::sensor("temperature", 23.5)
.with_metadata("location", "room_a")
.with_metadata("sensor_id", "temp_001");
assert_eq!(obs.metadata.len(), 2);
assert!(obs.metadata.contains_key("location"));
assert!(obs.metadata.contains_key("sensor_id"));
}
#[test]
fn test_observation_age_secs() {
let obs = Observation::sensor("temp", 20.0);
assert!(obs.age_secs() < 1);
}
#[test]
fn test_observation_is_recent() {
let obs = Observation::sensor("temp", 20.0);
assert!(obs.is_recent(10));
assert!(obs.is_recent(1));
}
#[test]
fn test_observation_clone() {
let obs = Observation::sensor("temp", 25.0).with_confidence(0.9);
let cloned = obs.clone();
assert_eq!(obs.value.as_f64(), cloned.value.as_f64());
assert!((obs.confidence.value() - cloned.confidence.value()).abs() < 0.001);
}
#[test]
fn test_observation_debug() {
let obs = Observation::sensor("temp", 25.0);
let debug_str = format!("{:?}", obs);
assert!(debug_str.contains("Observation"));
}
#[test]
fn test_observation_serialize() {
let obs = Observation::sensor("temp", 25.0);
let json = serde_json::to_string(&obs).unwrap();
assert!(json.contains("temp"));
let parsed: Observation = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.value.as_f64(), obs.value.as_f64());
}
#[test]
fn test_value_sensor_name() {
let sensor = ValueSensor::new("my_sensor");
assert_eq!(sensor.name(), "my_sensor");
}
#[test]
fn test_value_sensor_clear() {
let mut sensor = ValueSensor::new("temp");
sensor.set_value(25.0);
assert!(sensor.is_available());
sensor.clear();
assert!(!sensor.is_available());
assert!(sensor.read().is_none());
}
#[test]
fn test_value_sensor_multiple_values() {
let mut sensor = ValueSensor::new("temp");
sensor.set_value(20.0);
assert_eq!(sensor.read().unwrap().value.as_f64().unwrap(), 20.0);
sensor.set_value(25.0);
assert_eq!(sensor.read().unwrap().value.as_f64().unwrap(), 25.0);
sensor.set_value(30.0);
assert_eq!(sensor.read().unwrap().value.as_f64().unwrap(), 30.0);
}
#[test]
fn test_value_sensor_string_value() {
let mut sensor = ValueSensor::new("status");
sensor.set_value("active");
let obs = sensor.read().unwrap();
assert_eq!(obs.value.as_string(), "active");
}
#[test]
fn test_observation_buffer_new() {
let buffer = ObservationBuffer::new(100);
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
}
#[test]
fn test_observation_buffer_with_max_age() {
let buffer = ObservationBuffer::new(100).with_max_age(60);
assert_eq!(buffer.max_age_secs, 60);
}
#[test]
fn test_observation_buffer_push_capacity() {
let mut buffer = ObservationBuffer::new(3);
buffer.push(Observation::sensor("temp", 1.0));
buffer.push(Observation::sensor("temp", 2.0));
buffer.push(Observation::sensor("temp", 3.0));
assert_eq!(buffer.len(), 3);
buffer.push(Observation::sensor("temp", 4.0));
assert_eq!(buffer.len(), 3);
}
#[test]
fn test_observation_buffer_get_recent() {
let mut buffer = ObservationBuffer::new(10);
for i in 0..5 {
buffer.push(Observation::sensor("temp", i as f64));
}
let recent = buffer.get_recent(2);
assert_eq!(recent.len(), 2);
}
#[test]
fn test_observation_buffer_get_by_type() {
let mut buffer = ObservationBuffer::new(10);
buffer.push(Observation::sensor("temp", 20.0));
buffer.push(Observation::alert("High temp!"));
buffer.push(Observation::sensor("humidity", 60.0));
let sensors = buffer.get_by_type(&ObservationType::sensor("temp"));
assert_eq!(sensors.len(), 1);
}
#[test]
fn test_observation_buffer_latest() {
let mut buffer = ObservationBuffer::new(10);
assert!(buffer.latest().is_none());
buffer.push(Observation::sensor("temp", 1.0));
buffer.push(Observation::sensor("temp", 2.0));
let latest = buffer.latest().unwrap();
assert_eq!(latest.value.as_f64().unwrap(), 2.0);
}
#[test]
fn test_observation_buffer_clear() {
let mut buffer = ObservationBuffer::new(10);
buffer.push(Observation::sensor("temp", 20.0));
buffer.push(Observation::sensor("temp", 21.0));
assert_eq!(buffer.len(), 2);
buffer.clear();
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
}
#[test]
fn test_observation_buffer_is_empty() {
let mut buffer = ObservationBuffer::new(10);
assert!(buffer.is_empty());
buffer.push(Observation::sensor("temp", 20.0));
assert!(!buffer.is_empty());
}
}