Skip to main content

llm_optimizer_processor/normalization/
mod.rs

1//! Time-series normalization for stream processing
2//!
3//! This module provides production-grade time-series normalization capabilities including:
4//! - Multiple fill strategies (forward, backward, linear interpolation, zero, skip)
5//! - Timestamp alignment to regular intervals
6//! - Out-of-order event handling with buffering
7//! - Automatic interval detection
8//! - Batch and streaming normalization
9//! - Memory-efficient bounded buffers
10//! - Comprehensive metrics and tracing
11
12use 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// ============================================================================
20// Error Types
21// ============================================================================
22
23/// Errors that can occur during normalization
24#[derive(Debug, thiserror::Error)]
25pub enum NormalizationError {
26    /// Invalid interval duration
27    #[error("invalid interval: {interval}ms, must be greater than 0")]
28    InvalidInterval { interval: i64 },
29
30    /// Buffer overflow
31    #[error("buffer overflow: attempted to add event when buffer is full (capacity: {capacity})")]
32    BufferOverflow { capacity: usize },
33
34    /// Invalid timestamp
35    #[error("invalid timestamp: {timestamp}, reason: {reason}")]
36    InvalidTimestamp { timestamp: i64, reason: String },
37
38    /// Insufficient data for interpolation
39    #[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    /// Configuration error
47    #[error("configuration error: {0}")]
48    Configuration(String),
49
50    /// Computation error
51    #[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// ============================================================================
64// Fill Strategies
65// ============================================================================
66
67/// Strategy for filling missing time-series points
68#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(rename_all = "snake_case")]
70pub enum FillStrategy {
71    /// Carry forward the last observed value
72    ForwardFill,
73
74    /// Use the next observed value (requires future data)
75    BackwardFill,
76
77    /// Linear interpolation between surrounding points
78    LinearInterpolate,
79
80    /// Fill with zero/default value
81    Zero,
82
83    /// Skip missing points (no output for missing intervals)
84    Skip,
85}
86
87impl FillStrategy {
88    /// Fill a missing value given surrounding context
89    ///
90    /// # Arguments
91    /// * `prev_value` - The last observed value before the gap
92    /// * `next_value` - The next observed value after the gap
93    /// * `prev_time` - Timestamp of previous value
94    /// * `next_time` - Timestamp of next value
95    /// * `target_time` - Timestamp to fill
96    ///
97    /// # Returns
98    /// `Some(value)` if a value should be filled, `None` if point should be skipped
99    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                        // Perform linear interpolation
116                        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                            // If timestamps are identical, average the values
121                            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)),    // Fall back to forward fill
129                    (None, Some(v), _, _) => Ok(Some(v)),    // Fall back to backward fill
130                    (Some(v1), Some(v2), None, _) | (Some(v1), Some(v2), _, None) => Ok(Some((v1 + v2) / 2.0)), // No timestamps, average values
131                    (None, None, _, _) => Ok(None),          // No data available
132                }
133            }
134
135            FillStrategy::Zero => Ok(Some(0.0)),
136
137            FillStrategy::Skip => Ok(None),
138        }
139    }
140
141    /// Check if this strategy requires future data
142    pub fn requires_future_data(&self) -> bool {
143        matches!(self, FillStrategy::BackwardFill | FillStrategy::LinearInterpolate)
144    }
145}
146
147// ============================================================================
148// Configuration
149// ============================================================================
150
151/// Configuration for time-series normalization
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct NormalizationConfig {
154    /// Target interval for normalized time-series (in milliseconds)
155    /// If None, will be auto-detected from data
156    pub interval_ms: Option<i64>,
157
158    /// Strategy for filling missing values
159    pub fill_strategy: FillStrategy,
160
161    /// Maximum buffer size for out-of-order events
162    pub max_buffer_size: usize,
163
164    /// Maximum allowed out-of-orderness (in milliseconds)
165    pub max_out_of_order_ms: i64,
166
167    /// Minimum number of samples required for interval detection
168    pub min_samples_for_detection: usize,
169
170    /// Tolerance for interval detection (percentage)
171    pub interval_tolerance: f64,
172
173    /// Whether to emit warnings for late/out-of-order events
174    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, // 1 minute
184            min_samples_for_detection: 10,
185            interval_tolerance: 0.1, // 10% tolerance
186            warn_on_late_events: true,
187        }
188    }
189}
190
191impl NormalizationConfig {
192    /// Create a new configuration with specified interval
193    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    /// Set the fill strategy
204    pub fn with_fill_strategy(mut self, strategy: FillStrategy) -> Self {
205        self.fill_strategy = strategy;
206        self
207    }
208
209    /// Set the maximum buffer size
210    pub fn with_max_buffer_size(mut self, size: usize) -> Self {
211        self.max_buffer_size = size;
212        self
213    }
214
215    /// Validate the configuration
216    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// ============================================================================
246// Time-series Event
247// ============================================================================
248
249/// A time-series data point
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct TimeSeriesEvent {
252    /// Timestamp of the event
253    pub timestamp: DateTime<Utc>,
254
255    /// Value at this timestamp
256    pub value: f64,
257
258    /// Optional metadata/tags
259    pub metadata: Option<serde_json::Value>,
260}
261
262impl TimeSeriesEvent {
263    /// Create a new time-series event
264    pub fn new(timestamp: DateTime<Utc>, value: f64) -> Self {
265        Self {
266            timestamp,
267            value,
268            metadata: None,
269        }
270    }
271
272    /// Create an event with metadata
273    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// ============================================================================
283// Statistics
284// ============================================================================
285
286/// Statistics about normalization operations
287#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288pub struct NormalizationStats {
289    /// Total events received
290    pub events_received: u64,
291
292    /// Events successfully normalized
293    pub events_normalized: u64,
294
295    /// Events dropped due to being too late
296    pub events_dropped_late: u64,
297
298    /// Events dropped due to buffer overflow
299    pub events_dropped_overflow: u64,
300
301    /// Number of points filled
302    pub points_filled: u64,
303
304    /// Number of points skipped
305    pub points_skipped: u64,
306
307    /// Current buffer size
308    pub current_buffer_size: usize,
309
310    /// Maximum buffer size reached
311    pub max_buffer_size_reached: usize,
312
313    /// Detected interval (in milliseconds)
314    pub detected_interval_ms: Option<i64>,
315
316    /// Total processing time (microseconds)
317    pub total_processing_time_us: u64,
318
319    /// Number of out-of-order events
320    pub out_of_order_events: u64,
321}
322
323impl NormalizationStats {
324    /// Calculate the fill rate (percentage of points that were filled)
325    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    /// Calculate the drop rate (percentage of events dropped)
334    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    /// Calculate average processing time per event (microseconds)
344    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// ============================================================================
354// Time-series Normalizer
355// ============================================================================
356
357/// Thread-safe time-series normalizer with out-of-order handling
358#[derive(Clone)]
359pub struct TimeSeriesNormalizer {
360    config: Arc<NormalizationConfig>,
361    state: Arc<RwLock<NormalizerState>>,
362}
363
364/// Internal state for the normalizer
365#[derive(Debug)]
366struct NormalizerState {
367    /// Sorted buffer for out-of-order events
368    buffer: BTreeMap<i64, f64>,
369
370    /// Last emitted timestamp
371    last_emitted_timestamp: Option<DateTime<Utc>>,
372
373    /// Statistics
374    stats: NormalizationStats,
375
376    /// Detected interval (in milliseconds)
377    detected_interval: Option<i64>,
378
379    /// Sample timestamps for interval detection
380    sample_timestamps: VecDeque<i64>,
381}
382
383impl TimeSeriesNormalizer {
384    /// Create a new normalizer with the given configuration
385    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    /// Create a normalizer with default configuration
403    pub fn default_config() -> NormalizationResult<Self> {
404        Self::new(NormalizationConfig::default())
405    }
406
407    /// Process a single time-series event and return normalized events
408    ///
409    /// This method may return multiple events if there were gaps that needed to be filled.
410    /// It buffers out-of-order events and emits them when the watermark advances.
411    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        // Check if event is too late
423        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        // Add to interval detection samples
439        if state.detected_interval.is_none() {
440            self.update_interval_detection(&mut state, timestamp_ms);
441        }
442
443        // Check buffer capacity
444        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        // Check for out-of-order
454        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        // Add to buffer
461        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        // Emit normalized events
467        let result = self.emit_normalized_events(&mut state);
468
469        // Update statistics
470        let elapsed = start_time.elapsed();
471        state.stats.total_processing_time_us += elapsed.as_micros() as u64;
472
473        result
474    }
475
476    /// Process a batch of events
477    ///
478    /// This is more efficient than calling `normalize()` repeatedly as it can
479    /// optimize buffer operations and reduce lock contention.
480    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            // Check if event is too late
495            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            // Add to interval detection samples
511            if state.detected_interval.is_none() {
512                self.update_interval_detection(&mut state, timestamp_ms);
513            }
514
515            // Check buffer capacity
516            if state.buffer.len() >= self.config.max_buffer_size {
517                state.stats.events_dropped_overflow += 1;
518                continue;
519            }
520
521            // Check for out-of-order
522            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            // Add to buffer
529            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        // Emit all normalized events
537        let result = self.emit_normalized_events(&mut state);
538
539        // Update statistics
540        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    /// Flush any remaining buffered events
553    ///
554    /// This should be called at the end of a stream to ensure all buffered
555    /// events are processed. It will emit events even if gaps remain.
556    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        // Emit all buffered events
564        while !state.buffer.is_empty() {
565            if let Some((&timestamp_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(&timestamp_ms);
571                state.last_emitted_timestamp = Some(timestamp);
572                state.stats.events_normalized += 1;
573            }
574        }
575
576        Ok(result)
577    }
578
579    /// Get current statistics
580    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    /// Reset the normalizer state
589    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    /// Get the detected or configured interval
604    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    // ========================================================================
613    // Private helper methods
614    // ========================================================================
615
616    /// Update interval detection with a new timestamp
617    fn update_interval_detection(&self, state: &mut NormalizerState, timestamp_ms: i64) {
618        state.sample_timestamps.push_back(timestamp_ms);
619
620        // Keep only the required number of samples
621        while state.sample_timestamps.len() > self.config.min_samples_for_detection {
622            state.sample_timestamps.pop_front();
623        }
624
625        // Try to detect interval once we have enough samples
626        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    /// Detect the interval from a set of timestamps
636    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        // Calculate median interval
654        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        // Verify consistency: most intervals should be within tolerance of median
662        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            // At least 70% of intervals are consistent
672            Some(median)
673        } else {
674            None
675        }
676    }
677
678    /// Emit normalized events from the buffer
679    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                // No interval detected yet, keep buffering
684                return Ok(Vec::new());
685            }
686        };
687
688        let mut result = Vec::new();
689
690        // Determine which events we can safely emit
691        // We need to keep some buffer for strategies that need future data
692        let buffer_to_keep = if self.config.fill_strategy.requires_future_data() {
693            2 // Keep at least 2 events for interpolation
694        } else {
695            1 // Keep at least 1 event for forward fill
696        };
697
698        if state.buffer.len() <= buffer_to_keep {
699            return Ok(result);
700        }
701
702        // Get the timestamps we can process
703        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[&timestamp_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            // Check if we need to fill gaps
719            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                    // There's a gap, fill it
725                    let filled_events = self.fill_gap(
726                        state,
727                        last_emitted,
728                        timestamp,
729                        interval,
730                        value,
731                        i,
732                        &timestamps,
733                    )?;
734                    result.extend(filled_events);
735                }
736            }
737
738            // Emit the actual event
739            result.push(TimeSeriesEvent::new(timestamp, value));
740            state.last_emitted_timestamp = Some(timestamp);
741            state.stats.events_normalized += 1;
742
743            // Remove from buffer
744            state.buffer.remove(&timestamp_ms);
745        }
746
747        Ok(result)
748    }
749
750    /// Fill a gap between two timestamps
751    #[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
815// ============================================================================
816// Builder Pattern
817// ============================================================================
818
819/// Builder for TimeSeriesNormalizer
820pub struct NormalizerBuilder {
821    config: NormalizationConfig,
822}
823
824impl NormalizerBuilder {
825    /// Create a new builder
826    pub fn new() -> Self {
827        Self {
828            config: NormalizationConfig::default(),
829        }
830    }
831
832    /// Set the target interval
833    pub fn interval_ms(mut self, interval: i64) -> Self {
834        self.config.interval_ms = Some(interval);
835        self
836    }
837
838    /// Set the fill strategy
839    pub fn fill_strategy(mut self, strategy: FillStrategy) -> Self {
840        self.config.fill_strategy = strategy;
841        self
842    }
843
844    /// Set the maximum buffer size
845    pub fn max_buffer_size(mut self, size: usize) -> Self {
846        self.config.max_buffer_size = size;
847        self
848    }
849
850    /// Set the maximum out-of-order duration
851    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    /// Set the minimum samples for interval detection
857    pub fn min_samples_for_detection(mut self, count: usize) -> Self {
858        self.config.min_samples_for_detection = count;
859        self
860    }
861
862    /// Set the interval detection tolerance
863    pub fn interval_tolerance(mut self, tolerance: f64) -> Self {
864        self.config.interval_tolerance = tolerance;
865        self
866    }
867
868    /// Enable or disable late event warnings
869    pub fn warn_on_late_events(mut self, warn: bool) -> Self {
870        self.config.warn_on_late_events = warn;
871        self
872    }
873
874    /// Build the normalizer
875    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// ============================================================================
887// Tests
888// ============================================================================
889
890#[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)); // Midpoint interpolation
936    }
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        // Should emit events except the last one (kept in buffer)
1004        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), // Gap of 2 intervals
1018            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        // Flush to get remaining events
1028        let final_results = normalizer.flush().unwrap();
1029        all_results.extend(final_results);
1030
1031        // Should have original points + filled points
1032        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), // Out of order
1065        ];
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), // Advance watermark
1093            create_event(base_time + 500, 5.0),     // Too late (should be dropped)
1094        ];
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        // Add more events than buffer can hold
1115        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        // Some events should be dropped due to overflow or processed
1122        assert!(stats.events_received == 10);
1123    }
1124
1125    #[test]
1126    fn test_normalizer_interval_detection() {
1127        let config = NormalizationConfig {
1128            interval_ms: None, // Auto-detect
1129            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        // Send regular events
1138        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); // 20/80
1213        assert_eq!(stats.drop_rate(), 0.10); // 10/100
1214        assert_eq!(stats.avg_processing_time_us(), 100.0); // 10000/100
1215    }
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        // No previous value, should fall back to next value
1240        let result = strategy
1241            .fill(None, Some(20.0), None, Some(t1), t1)
1242            .unwrap();
1243        assert_eq!(result, Some(20.0));
1244
1245        // No next value, should fall back to previous value
1246        let result = strategy
1247            .fill(Some(10.0), None, Some(t1), None, t1)
1248            .unwrap();
1249        assert_eq!(result, Some(10.0));
1250
1251        // No values at all
1252        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}