use crate::collector::types::{KeyboardEvent, MouseEvent, MouseEventType, ShortcutEvent};
use crate::core::windowing::EventWindow;
use synheart_flux::behavior::types::{ScrollEvent, TapEvent, TypingEvent};
use synheart_flux::behavior::{BehaviorEvent, BehaviorEventType, BehaviorSession};
pub struct SensorBehaviorAdapter {
device_id: String,
timezone: String,
}
impl SensorBehaviorAdapter {
pub fn new(device_id: String, timezone: String) -> Self {
Self {
device_id,
timezone,
}
}
pub fn with_defaults() -> Self {
Self {
device_id: format!("sensor-{}", uuid::Uuid::new_v4()),
timezone: "UTC".to_string(),
}
}
pub fn convert(&self, session_id: &str, window: &EventWindow) -> BehaviorSession {
let mut events = Vec::new();
for kb_event in &window.keyboard_events {
if kb_event.is_key_down {
events.push(self.keyboard_to_behavior(kb_event));
}
}
for shortcut_event in &window.shortcut_events {
events.push(self.shortcut_to_behavior(shortcut_event));
}
for mouse_event in &window.mouse_events {
if let Some(behavior_event) = self.mouse_to_behavior(mouse_event) {
events.push(behavior_event);
}
}
events.sort_by_key(|e| e.timestamp);
BehaviorSession {
session_id: session_id.to_string(),
device_id: self.device_id.clone(),
timezone: self.timezone.clone(),
start_time: window.start,
end_time: window.end,
events,
}
}
fn keyboard_to_behavior(&self, kb: &KeyboardEvent) -> BehaviorEvent {
BehaviorEvent {
timestamp: kb.timestamp,
event_type: BehaviorEventType::Typing,
scroll: None,
tap: None,
swipe: None,
interruption: None,
typing: Some(TypingEvent {
typing_speed_cpm: None, cadence_stability: None,
duration_sec: None,
pause_count: None,
start_at: None,
end_at: None,
typing_tap_count: None,
mean_inter_tap_interval_ms: None,
typing_cadence_variability: None,
typing_cadence_stability: None,
typing_gap_count: None,
typing_gap_ratio: None,
typing_burstiness: None,
typing_activity_ratio: None,
typing_interaction_intensity: None,
deep_typing: None,
number_of_backspace: None,
number_of_delete: None,
number_of_cut: None,
number_of_paste: None,
number_of_copy: None,
}),
app_switch: None,
}
}
fn shortcut_to_behavior(&self, shortcut: &ShortcutEvent) -> BehaviorEvent {
BehaviorEvent {
timestamp: shortcut.timestamp,
event_type: BehaviorEventType::Typing,
scroll: None,
tap: None,
swipe: None,
interruption: None,
typing: Some(TypingEvent {
typing_speed_cpm: None,
cadence_stability: None,
duration_sec: None,
pause_count: None,
start_at: None,
end_at: None,
typing_tap_count: None,
mean_inter_tap_interval_ms: None,
typing_cadence_variability: None,
typing_cadence_stability: None,
typing_gap_count: None,
typing_gap_ratio: None,
typing_burstiness: None,
typing_activity_ratio: None,
typing_interaction_intensity: None,
deep_typing: None,
number_of_backspace: None,
number_of_delete: None,
number_of_cut: None,
number_of_paste: None,
number_of_copy: None,
}),
app_switch: None,
}
}
fn mouse_to_behavior(&self, mouse: &MouseEvent) -> Option<BehaviorEvent> {
match mouse.event_type {
MouseEventType::Move => {
Some(BehaviorEvent {
timestamp: mouse.timestamp,
event_type: BehaviorEventType::Scroll,
scroll: Some(ScrollEvent {
velocity: mouse.delta_magnitude,
direction: None, direction_reversal: false,
}),
tap: None,
swipe: None,
interruption: None,
typing: None,
app_switch: None,
})
}
MouseEventType::LeftClick | MouseEventType::RightClick => {
Some(BehaviorEvent {
timestamp: mouse.timestamp,
event_type: BehaviorEventType::Tap,
scroll: None,
tap: Some(TapEvent {
tap_duration_ms: Some(100), long_press: false,
}),
swipe: None,
interruption: None,
typing: None,
app_switch: None,
})
}
MouseEventType::Scroll => {
Some(BehaviorEvent {
timestamp: mouse.timestamp,
event_type: BehaviorEventType::Scroll,
scroll: Some(ScrollEvent {
velocity: mouse.delta_magnitude,
direction: None, direction_reversal: false,
}),
tap: None,
swipe: None,
interruption: None,
typing: None,
app_switch: None,
})
}
}
}
}
pub fn convert_to_behavior_session(
session_id: &str,
window: &EventWindow,
device_id: Option<&str>,
) -> BehaviorSession {
let adapter = match device_id {
Some(id) => SensorBehaviorAdapter::new(id.to_string(), "UTC".to_string()),
None => SensorBehaviorAdapter::with_defaults(),
};
adapter.convert(session_id, window)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, Utc};
#[test]
fn test_adapter_creation() {
let adapter = SensorBehaviorAdapter::with_defaults();
assert!(adapter.device_id.starts_with("sensor-"));
}
#[test]
fn test_empty_window_conversion() {
let adapter = SensorBehaviorAdapter::with_defaults();
let window = EventWindow::new(Utc::now(), Duration::seconds(10));
let session = adapter.convert("test-session", &window);
assert_eq!(session.session_id, "test-session");
assert!(session.events.is_empty());
}
}