diff --git a/src/job/mod.rs b/src/job/mod.rs index 63617c2..6afc82f 100644 --- a/src/job/mod.rs +++ b/src/job/mod.rs @@ -80,6 +80,18 @@ impl TimeIntervalTracker { } } + /// Check if the bucket for the given timestamp is already full. + /// This is a non-mutating check used to skip processing early. + pub fn is_full(&self, timestamp: &str) -> bool { + let key = self.bucket_key(timestamp); + let buckets = self.buckets.read().unwrap(); + if let Some(counter) = buckets.get(&key) { + counter.load(Ordering::SeqCst) >= self.max_photos + } else { + false + } + } + /// Compute the bucket key from a timestamp string. fn bucket_key(&self, timestamp: &str) -> String { // Parse date from ISO 8601 timestamp (e.g. "2024-01-15" or "2024-01-15T12:34:56Z") diff --git a/src/job/processing.rs b/src/job/processing.rs index 51cd93e..98f7572 100644 --- a/src/job/processing.rs +++ b/src/job/processing.rs @@ -85,6 +85,18 @@ pub async fn process_single_asset( // Create pipeline context let mut ctx = PipelineContext::new(asset_id.clone(), timestamp.clone(), face_data.clone()); + // Early check: skip if the time slot is already full (avoids wasting processing time) + if let Some(tracker) = time_interval { + if tracker.is_full(×tamp) { + tracing::debug!("Asset {} skipped early: time slot already full (timestamp: {})", asset_id, timestamp); + skip_stats.increment("time_interval"); + return AssetProcessResult::Skipped { + asset_id: asset_id.clone(), + reason: "Time interval too short".to_string(), + }; + } + } + // Check before download (potentially slow) if cancel_token.is_cancelled() { return AssetProcessResult::Cancelled {