1use crate::error::{ProcessorError, Result};
13use chrono::{DateTime, Duration, Utc};
14use serde::{Deserialize, Serialize};
15use std::collections::{BTreeMap, VecDeque};
16use std::sync::{Arc, RwLock};
17use tracing::{debug, trace, warn};
18
19#[derive(Debug, thiserror::Error)]
25pub enum NormalizationError {
26 #[error("invalid interval: {interval}ms, must be greater than 0")]
28 InvalidInterval { interval: i64 },
29
30 #[error("buffer overflow: attempted to add event when buffer is full (capacity: {capacity})")]
32 BufferOverflow { capacity: usize },
33
34 #[error("invalid timestamp: {timestamp}, reason: {reason}")]
36 InvalidTimestamp { timestamp: i64, reason: String },
37
38 #[error("insufficient data for {operation}: need at least {required} points, got {actual}")]
40 InsufficientData {
41 operation: String,
42 required: usize,
43 actual: usize,
44 },
45
46 #[error("configuration error: {0}")]
48 Configuration(String),
49
50 #[error("computation error: {0}")]
52 Computation(String),
53}
54
55impl From<NormalizationError> for ProcessorError {
56 fn from(err: NormalizationError) -> Self {
57 ProcessorError::Unexpected(err.to_string())
58 }
59}
60
61pub type NormalizationResult<T> = std::result::Result<T, NormalizationError>;
62
63#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(rename_all = "snake_case")]
70pub enum FillStrategy {
71 ForwardFill,
73
74 BackwardFill,
76
77 LinearInterpolate,
79
80 Zero,
82
83 Skip,
85}
86
87impl FillStrategy {
88 pub fn fill(
100 &self,
101 prev_value: Option<f64>,
102 next_value: Option<f64>,
103 prev_time: Option<DateTime<Utc>>,
104 next_time: Option<DateTime<Utc>>,
105 target_time: DateTime<Utc>,
106 ) -> NormalizationResult<Option<f64>> {
107 match self {
108 FillStrategy::ForwardFill => Ok(prev_value),
109
110 FillStrategy::BackwardFill => Ok(next_value),
111
112 FillStrategy::LinearInterpolate => {
113 match (prev_value, next_value, prev_time, next_time) {
114 (Some(v1), Some(v2), Some(t1), Some(t2)) => {
115 let total_duration = (t2 - t1).num_milliseconds() as f64;
117 let elapsed_duration = (target_time - t1).num_milliseconds() as f64;
118
119 if total_duration == 0.0 {
120 Ok(Some((v1 + v2) / 2.0))
122 } else {
123 let ratio = elapsed_duration / total_duration;
124 let interpolated = v1 + ratio * (v2 - v1);
125 Ok(Some(interpolated))
126 }
127 }
128 (Some(v), None, _, _) => Ok(Some(v)), (None, Some(v), _, _) => Ok(Some(v)), (Some(v1), Some(v2), None, _) | (Some(v1), Some(v2), _, None) => Ok(Some((v1 + v2) / 2.0)), (None, None, _, _) => Ok(None), }
133 }
134
135 FillStrategy::Zero => Ok(Some(0.0)),
136
137 FillStrategy::Skip => Ok(None),
138 }
139 }
140
141 pub fn requires_future_data(&self) -> bool {
143 matches!(self, FillStrategy::BackwardFill | FillStrategy::LinearInterpolate)
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct NormalizationConfig {
154 pub interval_ms: Option<i64>,
157
158 pub fill_strategy: FillStrategy,
160
161 pub max_buffer_size: usize,
163
164 pub max_out_of_order_ms: i64,
166
167 pub min_samples_for_detection: usize,
169
170 pub interval_tolerance: f64,
172
173 pub warn_on_late_events: bool,
175}
176
177impl Default for NormalizationConfig {
178 fn default() -> Self {
179 Self {
180 interval_ms: None,
181 fill_strategy: FillStrategy::LinearInterpolate,
182 max_buffer_size: 1000,
183 max_out_of_order_ms: 60_000, min_samples_for_detection: 10,
185 interval_tolerance: 0.1, warn_on_late_events: true,
187 }
188 }
189}
190
191impl NormalizationConfig {
192 pub fn with_interval(interval_ms: i64) -> NormalizationResult<Self> {
194 if interval_ms <= 0 {
195 return Err(NormalizationError::InvalidInterval { interval: interval_ms });
196 }
197 Ok(Self {
198 interval_ms: Some(interval_ms),
199 ..Default::default()
200 })
201 }
202
203 pub fn with_fill_strategy(mut self, strategy: FillStrategy) -> Self {
205 self.fill_strategy = strategy;
206 self
207 }
208
209 pub fn with_max_buffer_size(mut self, size: usize) -> Self {
211 self.max_buffer_size = size;
212 self
213 }
214
215 pub fn validate(&self) -> NormalizationResult<()> {
217 if let Some(interval) = self.interval_ms {
218 if interval <= 0 {
219 return Err(NormalizationError::InvalidInterval { interval });
220 }
221 }
222
223 if self.max_buffer_size == 0 {
224 return Err(NormalizationError::Configuration(
225 "max_buffer_size must be greater than 0".to_string(),
226 ));
227 }
228
229 if self.max_out_of_order_ms < 0 {
230 return Err(NormalizationError::Configuration(
231 "max_out_of_order_ms must be non-negative".to_string(),
232 ));
233 }
234
235 if self.interval_tolerance < 0.0 || self.interval_tolerance >= 1.0 {
236 return Err(NormalizationError::Configuration(
237 "interval_tolerance must be between 0.0 and 1.0".to_string(),
238 ));
239 }
240
241 Ok(())
242 }
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct TimeSeriesEvent {
252 pub timestamp: DateTime<Utc>,
254
255 pub value: f64,
257
258 pub metadata: Option<serde_json::Value>,
260}
261
262impl TimeSeriesEvent {
263 pub fn new(timestamp: DateTime<Utc>, value: f64) -> Self {
265 Self {
266 timestamp,
267 value,
268 metadata: None,
269 }
270 }
271
272 pub fn with_metadata(timestamp: DateTime<Utc>, value: f64, metadata: serde_json::Value) -> Self {
274 Self {
275 timestamp,
276 value,
277 metadata: Some(metadata),
278 }
279 }
280}
281
282#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288pub struct NormalizationStats {
289 pub events_received: u64,
291
292 pub events_normalized: u64,
294
295 pub events_dropped_late: u64,
297
298 pub events_dropped_overflow: u64,
300
301 pub points_filled: u64,
303
304 pub points_skipped: u64,
306
307 pub current_buffer_size: usize,
309
310 pub max_buffer_size_reached: usize,
312
313 pub detected_interval_ms: Option<i64>,
315
316 pub total_processing_time_us: u64,
318
319 pub out_of_order_events: u64,
321}
322
323impl NormalizationStats {
324 pub fn fill_rate(&self) -> f64 {
326 if self.events_normalized > 0 {
327 self.points_filled as f64 / self.events_normalized as f64
328 } else {
329 0.0
330 }
331 }
332
333 pub fn drop_rate(&self) -> f64 {
335 if self.events_received > 0 {
336 (self.events_dropped_late + self.events_dropped_overflow) as f64
337 / self.events_received as f64
338 } else {
339 0.0
340 }
341 }
342
343 pub fn avg_processing_time_us(&self) -> f64 {
345 if self.events_received > 0 {
346 self.total_processing_time_us as f64 / self.events_received as f64
347 } else {
348 0.0
349 }
350 }
351}
352
353#[derive(Clone)]
359pub struct TimeSeriesNormalizer {
360 config: Arc<NormalizationConfig>,
361 state: Arc<RwLock<NormalizerState>>,
362}
363
364#[derive(Debug)]
366struct NormalizerState {
367 buffer: BTreeMap<i64, f64>,
369
370 last_emitted_timestamp: Option<DateTime<Utc>>,
372
373 stats: NormalizationStats,
375
376 detected_interval: Option<i64>,
378
379 sample_timestamps: VecDeque<i64>,
381}
382
383impl TimeSeriesNormalizer {
384 pub fn new(config: NormalizationConfig) -> NormalizationResult<Self> {
386 config.validate()?;
387
388 let state = NormalizerState {
389 buffer: BTreeMap::new(),
390 last_emitted_timestamp: None,
391 stats: NormalizationStats::default(),
392 detected_interval: config.interval_ms,
393 sample_timestamps: VecDeque::with_capacity(config.min_samples_for_detection),
394 };
395
396 Ok(Self {
397 config: Arc::new(config),
398 state: Arc::new(RwLock::new(state)),
399 })
400 }
401
402 pub fn default_config() -> NormalizationResult<Self> {
404 Self::new(NormalizationConfig::default())
405 }
406
407 pub fn normalize(&self, event: TimeSeriesEvent) -> Result<Vec<TimeSeriesEvent>> {
412 let start_time = std::time::Instant::now();
413
414 let mut state = self.state.write().map_err(|e| {
415 ProcessorError::Unexpected(format!("Failed to acquire write lock: {}", e))
416 })?;
417
418 state.stats.events_received += 1;
419
420 let timestamp_ms = event.timestamp.timestamp_millis();
421
422 if let Some(last_emitted) = state.last_emitted_timestamp {
424 let late_by_ms = last_emitted.timestamp_millis() - timestamp_ms;
425 if late_by_ms > self.config.max_out_of_order_ms {
426 if self.config.warn_on_late_events {
427 warn!(
428 timestamp_ms = timestamp_ms,
429 late_by_ms = late_by_ms,
430 "Dropping late event"
431 );
432 }
433 state.stats.events_dropped_late += 1;
434 return Ok(Vec::new());
435 }
436 }
437
438 if state.detected_interval.is_none() {
440 self.update_interval_detection(&mut state, timestamp_ms);
441 }
442
443 if state.buffer.len() >= self.config.max_buffer_size {
445 warn!(
446 buffer_size = state.buffer.len(),
447 "Buffer overflow, dropping event"
448 );
449 state.stats.events_dropped_overflow += 1;
450 return Ok(Vec::new());
451 }
452
453 if let Some(last_emitted) = state.last_emitted_timestamp {
455 if timestamp_ms < last_emitted.timestamp_millis() {
456 state.stats.out_of_order_events += 1;
457 }
458 }
459
460 state.buffer.insert(timestamp_ms, event.value);
462 state.stats.current_buffer_size = state.buffer.len();
463 state.stats.max_buffer_size_reached =
464 state.stats.max_buffer_size_reached.max(state.buffer.len());
465
466 let result = self.emit_normalized_events(&mut state);
468
469 let elapsed = start_time.elapsed();
471 state.stats.total_processing_time_us += elapsed.as_micros() as u64;
472
473 result
474 }
475
476 pub fn normalize_batch(&self, events: Vec<TimeSeriesEvent>) -> Result<Vec<TimeSeriesEvent>> {
481 let start_time = std::time::Instant::now();
482
483 let mut state = self.state.write().map_err(|e| {
484 ProcessorError::Unexpected(format!("Failed to acquire write lock: {}", e))
485 })?;
486
487 let mut all_results = Vec::new();
488
489 for event in events {
490 state.stats.events_received += 1;
491
492 let timestamp_ms = event.timestamp.timestamp_millis();
493
494 if let Some(last_emitted) = state.last_emitted_timestamp {
496 let late_by_ms = last_emitted.timestamp_millis() - timestamp_ms;
497 if late_by_ms > self.config.max_out_of_order_ms {
498 if self.config.warn_on_late_events {
499 debug!(
500 timestamp_ms = timestamp_ms,
501 late_by_ms = late_by_ms,
502 "Dropping late event in batch"
503 );
504 }
505 state.stats.events_dropped_late += 1;
506 continue;
507 }
508 }
509
510 if state.detected_interval.is_none() {
512 self.update_interval_detection(&mut state, timestamp_ms);
513 }
514
515 if state.buffer.len() >= self.config.max_buffer_size {
517 state.stats.events_dropped_overflow += 1;
518 continue;
519 }
520
521 if let Some(last_emitted) = state.last_emitted_timestamp {
523 if timestamp_ms < last_emitted.timestamp_millis() {
524 state.stats.out_of_order_events += 1;
525 }
526 }
527
528 state.buffer.insert(timestamp_ms, event.value);
530 }
531
532 state.stats.current_buffer_size = state.buffer.len();
533 state.stats.max_buffer_size_reached =
534 state.stats.max_buffer_size_reached.max(state.buffer.len());
535
536 let result = self.emit_normalized_events(&mut state);
538
539 let elapsed = start_time.elapsed();
541 state.stats.total_processing_time_us += elapsed.as_micros() as u64;
542
543 match result {
544 Ok(events) => {
545 all_results.extend(events);
546 Ok(all_results)
547 }
548 Err(e) => Err(e),
549 }
550 }
551
552 pub fn flush(&self) -> Result<Vec<TimeSeriesEvent>> {
557 let mut state = self.state.write().map_err(|e| {
558 ProcessorError::Unexpected(format!("Failed to acquire write lock: {}", e))
559 })?;
560
561 let mut result = Vec::new();
562
563 while !state.buffer.is_empty() {
565 if let Some((×tamp_ms, &value)) = state.buffer.iter().next() {
566 let timestamp = DateTime::from_timestamp_millis(timestamp_ms)
567 .unwrap_or_else(|| Utc::now());
568
569 result.push(TimeSeriesEvent::new(timestamp, value));
570 state.buffer.remove(×tamp_ms);
571 state.last_emitted_timestamp = Some(timestamp);
572 state.stats.events_normalized += 1;
573 }
574 }
575
576 Ok(result)
577 }
578
579 pub fn stats(&self) -> Result<NormalizationStats> {
581 let state = self.state.read().map_err(|e| {
582 ProcessorError::Unexpected(format!("Failed to acquire read lock: {}", e))
583 })?;
584
585 Ok(state.stats.clone())
586 }
587
588 pub fn reset(&self) -> Result<()> {
590 let mut state = self.state.write().map_err(|e| {
591 ProcessorError::Unexpected(format!("Failed to acquire write lock: {}", e))
592 })?;
593
594 state.buffer.clear();
595 state.last_emitted_timestamp = None;
596 state.stats = NormalizationStats::default();
597 state.detected_interval = self.config.interval_ms;
598 state.sample_timestamps.clear();
599
600 Ok(())
601 }
602
603 pub fn interval_ms(&self) -> Result<Option<i64>> {
605 let state = self.state.read().map_err(|e| {
606 ProcessorError::Unexpected(format!("Failed to acquire read lock: {}", e))
607 })?;
608
609 Ok(state.detected_interval)
610 }
611
612 fn update_interval_detection(&self, state: &mut NormalizerState, timestamp_ms: i64) {
618 state.sample_timestamps.push_back(timestamp_ms);
619
620 while state.sample_timestamps.len() > self.config.min_samples_for_detection {
622 state.sample_timestamps.pop_front();
623 }
624
625 if state.sample_timestamps.len() >= self.config.min_samples_for_detection {
627 if let Some(interval) = self.detect_interval(&state.sample_timestamps) {
628 debug!(interval_ms = interval, "Detected time-series interval");
629 state.detected_interval = Some(interval);
630 state.stats.detected_interval_ms = Some(interval);
631 }
632 }
633 }
634
635 fn detect_interval(&self, timestamps: &VecDeque<i64>) -> Option<i64> {
637 if timestamps.len() < 2 {
638 return None;
639 }
640
641 let mut intervals = Vec::new();
642 for i in 1..timestamps.len() {
643 let interval = timestamps[i] - timestamps[i - 1];
644 if interval > 0 {
645 intervals.push(interval);
646 }
647 }
648
649 if intervals.is_empty() {
650 return None;
651 }
652
653 intervals.sort_unstable();
655 let median = if intervals.len() % 2 == 0 {
656 (intervals[intervals.len() / 2 - 1] + intervals[intervals.len() / 2]) / 2
657 } else {
658 intervals[intervals.len() / 2]
659 };
660
661 let tolerance = (median as f64 * self.config.interval_tolerance) as i64;
663 let consistent_count = intervals
664 .iter()
665 .filter(|&&i| (i - median).abs() <= tolerance)
666 .count();
667
668 let consistency_ratio = consistent_count as f64 / intervals.len() as f64;
669
670 if consistency_ratio >= 0.7 {
671 Some(median)
673 } else {
674 None
675 }
676 }
677
678 fn emit_normalized_events(&self, state: &mut NormalizerState) -> Result<Vec<TimeSeriesEvent>> {
680 let interval = match state.detected_interval {
681 Some(i) => i,
682 None => {
683 return Ok(Vec::new());
685 }
686 };
687
688 let mut result = Vec::new();
689
690 let buffer_to_keep = if self.config.fill_strategy.requires_future_data() {
693 2 } else {
695 1 };
697
698 if state.buffer.len() <= buffer_to_keep {
699 return Ok(result);
700 }
701
702 let timestamps: Vec<i64> = state.buffer.keys().copied().collect();
704 let emit_until_idx = timestamps.len() - buffer_to_keep;
705
706 for i in 0..emit_until_idx {
707 let timestamp_ms = timestamps[i];
708 let value = state.buffer[×tamp_ms];
709
710 let timestamp = DateTime::from_timestamp_millis(timestamp_ms)
711 .ok_or_else(|| {
712 NormalizationError::InvalidTimestamp {
713 timestamp: timestamp_ms,
714 reason: "invalid timestamp".to_string(),
715 }
716 })?;
717
718 if let Some(last_emitted) = state.last_emitted_timestamp {
720 let expected_next_ms = last_emitted.timestamp_millis() + interval;
721 let gap_ms = timestamp_ms - expected_next_ms;
722
723 if gap_ms > 0 {
724 let filled_events = self.fill_gap(
726 state,
727 last_emitted,
728 timestamp,
729 interval,
730 value,
731 i,
732 ×tamps,
733 )?;
734 result.extend(filled_events);
735 }
736 }
737
738 result.push(TimeSeriesEvent::new(timestamp, value));
740 state.last_emitted_timestamp = Some(timestamp);
741 state.stats.events_normalized += 1;
742
743 state.buffer.remove(×tamp_ms);
745 }
746
747 Ok(result)
748 }
749
750 #[allow(clippy::too_many_arguments)]
752 fn fill_gap(
753 &self,
754 state: &mut NormalizerState,
755 last_timestamp: DateTime<Utc>,
756 next_timestamp: DateTime<Utc>,
757 interval: i64,
758 next_value: f64,
759 next_idx: usize,
760 all_timestamps: &[i64],
761 ) -> Result<Vec<TimeSeriesEvent>> {
762 let mut result = Vec::new();
763
764 let prev_value = if next_idx > 0 {
765 Some(state.buffer[&all_timestamps[next_idx - 1]])
766 } else {
767 None
768 };
769
770 let mut current_ms = last_timestamp.timestamp_millis() + interval;
771 let next_ms = next_timestamp.timestamp_millis();
772
773 while current_ms < next_ms {
774 let current_timestamp = DateTime::from_timestamp_millis(current_ms)
775 .ok_or_else(|| NormalizationError::InvalidTimestamp {
776 timestamp: current_ms,
777 reason: "invalid timestamp during gap fill".to_string(),
778 })?;
779
780 let filled_value = self.config.fill_strategy.fill(
781 prev_value,
782 Some(next_value),
783 Some(last_timestamp),
784 Some(next_timestamp),
785 current_timestamp,
786 )?;
787
788 match filled_value {
789 Some(value) => {
790 result.push(TimeSeriesEvent::new(current_timestamp, value));
791 state.stats.points_filled += 1;
792 trace!(
793 timestamp_ms = current_ms,
794 value = value,
795 strategy = ?self.config.fill_strategy,
796 "Filled missing point"
797 );
798 }
799 None => {
800 state.stats.points_skipped += 1;
801 trace!(
802 timestamp_ms = current_ms,
803 "Skipped missing point"
804 );
805 }
806 }
807
808 current_ms += interval;
809 }
810
811 Ok(result)
812 }
813}
814
815pub struct NormalizerBuilder {
821 config: NormalizationConfig,
822}
823
824impl NormalizerBuilder {
825 pub fn new() -> Self {
827 Self {
828 config: NormalizationConfig::default(),
829 }
830 }
831
832 pub fn interval_ms(mut self, interval: i64) -> Self {
834 self.config.interval_ms = Some(interval);
835 self
836 }
837
838 pub fn fill_strategy(mut self, strategy: FillStrategy) -> Self {
840 self.config.fill_strategy = strategy;
841 self
842 }
843
844 pub fn max_buffer_size(mut self, size: usize) -> Self {
846 self.config.max_buffer_size = size;
847 self
848 }
849
850 pub fn max_out_of_order_ms(mut self, ms: i64) -> Self {
852 self.config.max_out_of_order_ms = ms;
853 self
854 }
855
856 pub fn min_samples_for_detection(mut self, count: usize) -> Self {
858 self.config.min_samples_for_detection = count;
859 self
860 }
861
862 pub fn interval_tolerance(mut self, tolerance: f64) -> Self {
864 self.config.interval_tolerance = tolerance;
865 self
866 }
867
868 pub fn warn_on_late_events(mut self, warn: bool) -> Self {
870 self.config.warn_on_late_events = warn;
871 self
872 }
873
874 pub fn build(self) -> NormalizationResult<TimeSeriesNormalizer> {
876 TimeSeriesNormalizer::new(self.config)
877 }
878}
879
880impl Default for NormalizerBuilder {
881 fn default() -> Self {
882 Self::new()
883 }
884}
885
886#[cfg(test)]
891mod tests {
892 use super::*;
893
894 fn create_event(timestamp_ms: i64, value: f64) -> TimeSeriesEvent {
895 let timestamp = DateTime::from_timestamp_millis(timestamp_ms).unwrap();
896 TimeSeriesEvent::new(timestamp, value)
897 }
898
899 #[test]
900 fn test_fill_strategy_forward_fill() {
901 let strategy = FillStrategy::ForwardFill;
902 let t1 = Utc::now();
903 let t2 = t1 + Duration::seconds(10);
904 let target = t1 + Duration::seconds(5);
905
906 let result = strategy
907 .fill(Some(10.0), Some(20.0), Some(t1), Some(t2), target)
908 .unwrap();
909 assert_eq!(result, Some(10.0));
910 }
911
912 #[test]
913 fn test_fill_strategy_backward_fill() {
914 let strategy = FillStrategy::BackwardFill;
915 let t1 = Utc::now();
916 let t2 = t1 + Duration::seconds(10);
917 let target = t1 + Duration::seconds(5);
918
919 let result = strategy
920 .fill(Some(10.0), Some(20.0), Some(t1), Some(t2), target)
921 .unwrap();
922 assert_eq!(result, Some(20.0));
923 }
924
925 #[test]
926 fn test_fill_strategy_linear_interpolate() {
927 let strategy = FillStrategy::LinearInterpolate;
928 let t1 = Utc::now();
929 let t2 = t1 + Duration::seconds(10);
930 let target = t1 + Duration::seconds(5);
931
932 let result = strategy
933 .fill(Some(10.0), Some(20.0), Some(t1), Some(t2), target)
934 .unwrap();
935 assert_eq!(result, Some(15.0)); }
937
938 #[test]
939 fn test_fill_strategy_zero() {
940 let strategy = FillStrategy::Zero;
941 let t1 = Utc::now();
942 let t2 = t1 + Duration::seconds(10);
943 let target = t1 + Duration::seconds(5);
944
945 let result = strategy
946 .fill(Some(10.0), Some(20.0), Some(t1), Some(t2), target)
947 .unwrap();
948 assert_eq!(result, Some(0.0));
949 }
950
951 #[test]
952 fn test_fill_strategy_skip() {
953 let strategy = FillStrategy::Skip;
954 let t1 = Utc::now();
955 let t2 = t1 + Duration::seconds(10);
956 let target = t1 + Duration::seconds(5);
957
958 let result = strategy
959 .fill(Some(10.0), Some(20.0), Some(t1), Some(t2), target)
960 .unwrap();
961 assert_eq!(result, None);
962 }
963
964 #[test]
965 fn test_config_validation() {
966 let config = NormalizationConfig::with_interval(1000).unwrap();
967 assert!(config.validate().is_ok());
968
969 let invalid_config = NormalizationConfig {
970 interval_ms: Some(-1000),
971 ..Default::default()
972 };
973 assert!(invalid_config.validate().is_err());
974 }
975
976 #[test]
977 fn test_normalizer_creation() {
978 let config = NormalizationConfig::with_interval(1000).unwrap();
979 let normalizer = TimeSeriesNormalizer::new(config);
980 assert!(normalizer.is_ok());
981 }
982
983 #[test]
984 fn test_normalizer_with_ordered_events() {
985 let config = NormalizationConfig::with_interval(1000)
986 .unwrap()
987 .with_fill_strategy(FillStrategy::Skip);
988 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
989
990 let base_time = 1000_i64;
991 let events = vec![
992 create_event(base_time, 10.0),
993 create_event(base_time + 1000, 20.0),
994 create_event(base_time + 2000, 30.0),
995 ];
996
997 let mut all_results = Vec::new();
998 for event in events {
999 let results = normalizer.normalize(event).unwrap();
1000 all_results.extend(results);
1001 }
1002
1003 assert!(all_results.len() >= 1);
1005 }
1006
1007 #[test]
1008 fn test_normalizer_with_gaps() {
1009 let config = NormalizationConfig::with_interval(1000)
1010 .unwrap()
1011 .with_fill_strategy(FillStrategy::LinearInterpolate);
1012 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1013
1014 let base_time = 1000_i64;
1015 let events = vec![
1016 create_event(base_time, 10.0),
1017 create_event(base_time + 3000, 40.0), create_event(base_time + 4000, 50.0),
1019 ];
1020
1021 let mut all_results = Vec::new();
1022 for event in events {
1023 let results = normalizer.normalize(event).unwrap();
1024 all_results.extend(results);
1025 }
1026
1027 let final_results = normalizer.flush().unwrap();
1029 all_results.extend(final_results);
1030
1031 assert!(all_results.len() >= 3);
1033 }
1034
1035 #[test]
1036 fn test_normalizer_batch_processing() {
1037 let config = NormalizationConfig::with_interval(1000)
1038 .unwrap()
1039 .with_fill_strategy(FillStrategy::Skip);
1040 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1041
1042 let base_time = 1000_i64;
1043 let events = vec![
1044 create_event(base_time, 10.0),
1045 create_event(base_time + 1000, 20.0),
1046 create_event(base_time + 2000, 30.0),
1047 ];
1048
1049 let results = normalizer.normalize_batch(events).unwrap();
1050 assert!(!results.is_empty());
1051 }
1052
1053 #[test]
1054 fn test_normalizer_out_of_order_events() {
1055 let config = NormalizationConfig::with_interval(1000)
1056 .unwrap()
1057 .with_fill_strategy(FillStrategy::Skip);
1058 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1059
1060 let base_time = 1000_i64;
1061 let events = vec![
1062 create_event(base_time, 10.0),
1063 create_event(base_time + 2000, 30.0),
1064 create_event(base_time + 1000, 20.0), ];
1066
1067 let mut all_results = Vec::new();
1068 for event in events {
1069 let results = normalizer.normalize(event).unwrap();
1070 all_results.extend(results);
1071 }
1072
1073 let stats = normalizer.stats().unwrap();
1074 assert_eq!(stats.out_of_order_events, 1);
1075 }
1076
1077 #[test]
1078 fn test_normalizer_late_events() {
1079 let config = NormalizationConfig {
1080 interval_ms: Some(1000),
1081 max_out_of_order_ms: 2000,
1082 warn_on_late_events: false,
1083 ..Default::default()
1084 };
1085 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1086
1087 let base_time = 1000_i64;
1088 let events = vec![
1089 create_event(base_time, 10.0),
1090 create_event(base_time + 1000, 20.0),
1091 create_event(base_time + 2000, 30.0),
1092 create_event(base_time + 10000, 100.0), create_event(base_time + 500, 5.0), ];
1095
1096 for event in events {
1097 let _ = normalizer.normalize(event);
1098 }
1099
1100 let stats = normalizer.stats().unwrap();
1101 assert!(stats.events_dropped_late > 0);
1102 }
1103
1104 #[test]
1105 fn test_normalizer_buffer_overflow() {
1106 let config = NormalizationConfig {
1107 interval_ms: Some(1000),
1108 max_buffer_size: 3,
1109 ..Default::default()
1110 };
1111 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1112
1113 let base_time = 1000_i64;
1114 for i in 0..10 {
1116 let event = create_event(base_time + (i * 1000), i as f64);
1117 let _ = normalizer.normalize(event);
1118 }
1119
1120 let stats = normalizer.stats().unwrap();
1121 assert!(stats.events_received == 10);
1123 }
1124
1125 #[test]
1126 fn test_normalizer_interval_detection() {
1127 let config = NormalizationConfig {
1128 interval_ms: None, min_samples_for_detection: 5,
1130 ..Default::default()
1131 };
1132 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1133
1134 let base_time = 1000_i64;
1135 let interval = 5000_i64;
1136
1137 for i in 0..10 {
1139 let event = create_event(base_time + (i * interval), i as f64);
1140 let _ = normalizer.normalize(event);
1141 }
1142
1143 let detected = normalizer.interval_ms().unwrap();
1144 assert_eq!(detected, Some(interval));
1145 }
1146
1147 #[test]
1148 fn test_normalizer_reset() {
1149 let config = NormalizationConfig::with_interval(1000).unwrap();
1150 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1151
1152 let event = create_event(1000, 10.0);
1153 let _ = normalizer.normalize(event);
1154
1155 let stats_before = normalizer.stats().unwrap();
1156 assert!(stats_before.events_received > 0);
1157
1158 normalizer.reset().unwrap();
1159
1160 let stats_after = normalizer.stats().unwrap();
1161 assert_eq!(stats_after.events_received, 0);
1162 }
1163
1164 #[test]
1165 fn test_normalizer_flush() {
1166 let config = NormalizationConfig::with_interval(1000)
1167 .unwrap()
1168 .with_fill_strategy(FillStrategy::Skip);
1169 let normalizer = TimeSeriesNormalizer::new(config).unwrap();
1170
1171 let base_time = 1000_i64;
1172 let events = vec![
1173 create_event(base_time, 10.0),
1174 create_event(base_time + 1000, 20.0),
1175 ];
1176
1177 for event in events {
1178 let _ = normalizer.normalize(event);
1179 }
1180
1181 let flushed = normalizer.flush().unwrap();
1182 assert!(!flushed.is_empty());
1183
1184 let stats = normalizer.stats().unwrap();
1185 assert_eq!(stats.current_buffer_size, 0);
1186 }
1187
1188 #[test]
1189 fn test_builder_pattern() {
1190 let normalizer = NormalizerBuilder::new()
1191 .interval_ms(1000)
1192 .fill_strategy(FillStrategy::ForwardFill)
1193 .max_buffer_size(500)
1194 .max_out_of_order_ms(30000)
1195 .build();
1196
1197 assert!(normalizer.is_ok());
1198 }
1199
1200 #[test]
1201 fn test_stats_calculations() {
1202 let mut stats = NormalizationStats {
1203 events_received: 100,
1204 events_normalized: 80,
1205 points_filled: 20,
1206 events_dropped_late: 5,
1207 events_dropped_overflow: 5,
1208 total_processing_time_us: 10000,
1209 ..Default::default()
1210 };
1211
1212 assert_eq!(stats.fill_rate(), 0.25); assert_eq!(stats.drop_rate(), 0.10); assert_eq!(stats.avg_processing_time_us(), 100.0); }
1216
1217 #[test]
1218 fn test_time_series_event_creation() {
1219 let timestamp = Utc::now();
1220 let event = TimeSeriesEvent::new(timestamp, 42.0);
1221
1222 assert_eq!(event.timestamp, timestamp);
1223 assert_eq!(event.value, 42.0);
1224 assert!(event.metadata.is_none());
1225
1226 let event_with_meta = TimeSeriesEvent::with_metadata(
1227 timestamp,
1228 42.0,
1229 serde_json::json!({"key": "value"}),
1230 );
1231 assert!(event_with_meta.metadata.is_some());
1232 }
1233
1234 #[test]
1235 fn test_linear_interpolation_edge_cases() {
1236 let strategy = FillStrategy::LinearInterpolate;
1237 let t1 = Utc::now();
1238
1239 let result = strategy
1241 .fill(None, Some(20.0), None, Some(t1), t1)
1242 .unwrap();
1243 assert_eq!(result, Some(20.0));
1244
1245 let result = strategy
1247 .fill(Some(10.0), None, Some(t1), None, t1)
1248 .unwrap();
1249 assert_eq!(result, Some(10.0));
1250
1251 let result = strategy.fill(None, None, None, None, t1).unwrap();
1253 assert_eq!(result, None);
1254 }
1255
1256 #[test]
1257 fn test_concurrent_access() {
1258 use std::thread;
1259
1260 let config = NormalizationConfig::with_interval(1000).unwrap();
1261 let normalizer = Arc::new(TimeSeriesNormalizer::new(config).unwrap());
1262
1263 let mut handles = vec![];
1264
1265 for i in 0..4 {
1266 let normalizer_clone = Arc::clone(&normalizer);
1267 let handle = thread::spawn(move || {
1268 let base_time = 1000 + (i * 10000);
1269 for j in 0..10 {
1270 let event = create_event(base_time + (j * 1000), (i * 10 + j) as f64);
1271 let _ = normalizer_clone.normalize(event);
1272 }
1273 });
1274 handles.push(handle);
1275 }
1276
1277 for handle in handles {
1278 handle.join().unwrap();
1279 }
1280
1281 let stats = normalizer.stats().unwrap();
1282 assert_eq!(stats.events_received, 40);
1283 }
1284}