use crate::core::features::WindowFeatures;
use crate::core::hsi::HsiSnapshot;
use crate::core::windowing::EventWindow;
use crate::flux::adapter::SensorBehaviorAdapter;
use serde::{Deserialize, Serialize};
use synheart_flux::behavior::BehaviorProcessor;
use synheart_flux::ComputeError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnrichedSnapshot {
pub base: HsiSnapshot,
#[serde(skip_serializing_if = "Option::is_none")]
pub flux_behavior: Option<FluxBehaviorMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub baseline: Option<BaselineInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FluxBehaviorMetrics {
pub distraction_score: f64,
pub focus_hint: f64,
pub task_switch_rate: f64,
pub notification_load: f64,
pub burstiness: f64,
pub scroll_jitter_rate: f64,
pub interaction_intensity: f64,
pub deep_focus_blocks: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaselineInfo {
pub distraction: Option<f64>,
pub focus: Option<f64>,
pub distraction_deviation_pct: Option<f64>,
pub sessions_in_baseline: u32,
}
pub struct SensorFluxProcessor {
processor: BehaviorProcessor,
adapter: SensorBehaviorAdapter,
session_count: usize,
}
impl SensorFluxProcessor {
pub fn new(baseline_window_sessions: usize) -> Self {
Self {
processor: BehaviorProcessor::with_baseline_window(baseline_window_sessions),
adapter: SensorBehaviorAdapter::with_defaults(),
session_count: 0,
}
}
pub fn with_device_id(baseline_window_sessions: usize, device_id: &str) -> Self {
Self {
processor: BehaviorProcessor::with_baseline_window(baseline_window_sessions),
adapter: SensorBehaviorAdapter::new(device_id.to_string(), "UTC".to_string()),
session_count: 0,
}
}
pub fn process_window(
&mut self,
window: &EventWindow,
_features: &WindowFeatures,
base_snapshot: HsiSnapshot,
) -> Result<EnrichedSnapshot, ComputeError> {
self.session_count += 1;
let session_id = format!("sensor-{}", self.session_count);
let session = self.adapter.convert(&session_id, window);
let session_json = serde_json::to_string(&session)
.map_err(|e| ComputeError::EncodingError(e.to_string()))?;
let hsi_json = self.processor.process(&session_json)?;
let (flux_behavior, baseline) = extract_flux_metrics_from_json(&hsi_json)?;
Ok(EnrichedSnapshot {
base: base_snapshot,
flux_behavior,
baseline,
})
}
pub fn update_baseline(&mut self, window: &EventWindow) -> Result<(), ComputeError> {
self.session_count += 1;
let session_id = format!("sensor-{}", self.session_count);
let session = self.adapter.convert(&session_id, window);
let session_json = serde_json::to_string(&session)
.map_err(|e| ComputeError::EncodingError(e.to_string()))?;
let _ = self.processor.process(&session_json)?;
Ok(())
}
pub fn save_baselines(&self) -> Result<String, ComputeError> {
self.processor.save_baselines()
}
pub fn load_baselines(&mut self, json: &str) -> Result<(), ComputeError> {
self.processor.load_baselines(json)
}
pub fn session_count(&self) -> usize {
self.session_count
}
}
fn extract_flux_metrics_from_json(
hsi_json: &str,
) -> Result<(Option<FluxBehaviorMetrics>, Option<BaselineInfo>), ComputeError> {
let payload: serde_json::Value =
serde_json::from_str(hsi_json).map_err(|e| ComputeError::ParseError(e.to_string()))?;
let windows = payload.get("behavior_windows").and_then(|w| w.as_array());
let window = match windows {
Some(w) if !w.is_empty() => &w[0],
_ => return Ok((None, None)),
};
let behavior = window.get("behavior");
let flux_behavior = behavior.map(|b| FluxBehaviorMetrics {
distraction_score: b
.get("distraction_score")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
focus_hint: b.get("focus_hint").and_then(|v| v.as_f64()).unwrap_or(0.0),
task_switch_rate: b
.get("task_switch_rate")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
notification_load: b
.get("notification_load")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
burstiness: b.get("burstiness").and_then(|v| v.as_f64()).unwrap_or(0.0),
scroll_jitter_rate: b
.get("scroll_jitter_rate")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
interaction_intensity: b
.get("interaction_intensity")
.and_then(|v| v.as_f64())
.unwrap_or(0.0),
deep_focus_blocks: b
.get("deep_focus_blocks")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32,
});
let baseline_json = window.get("baseline");
let baseline = baseline_json.map(|b| BaselineInfo {
distraction: b.get("distraction").and_then(|v| v.as_f64()),
focus: b.get("focus").and_then(|v| v.as_f64()),
distraction_deviation_pct: b.get("distraction_deviation_pct").and_then(|v| v.as_f64()),
sessions_in_baseline: b
.get("sessions_in_baseline")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32,
});
Ok((flux_behavior, baseline))
}
impl Default for SensorFluxProcessor {
fn default() -> Self {
Self::new(20)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_processor_creation() {
let processor = SensorFluxProcessor::new(20);
assert_eq!(processor.session_count(), 0);
}
#[test]
fn test_processor_with_device_id() {
let processor = SensorFluxProcessor::with_device_id(20, "test-device");
assert_eq!(processor.session_count(), 0);
}
}