# frozen_string_literal: true # Service to split time ranges into processable chunks for parallel track generation # Handles buffer zones to ensure tracks spanning multiple chunks are properly processed class Tracks::TimeChunker attr_reader :user, :start_at, :end_at, :chunk_size, :buffer_size def initialize(user, start_at: nil, end_at: nil, chunk_size: 1.day, buffer_size: 6.hours) @user = user @start_at = start_at @end_at = end_at @chunk_size = chunk_size @buffer_size = buffer_size end def call time_range = determine_time_range return [] if time_range.nil? start_time, end_time = time_range return [] if start_time >= end_time chunks = [] current_time = start_time while current_time < end_time chunk_end = [current_time + chunk_size, end_time].min chunk = create_chunk(current_time, chunk_end, start_time, end_time) chunks << chunk if chunk_has_points?(chunk) current_time = chunk_end end chunks end private def determine_time_range case when start_at && end_at [start_at.to_time, end_at.to_time] when start_at [start_at.to_time, Time.current] when end_at first_point_time = user.points.minimum(:timestamp) return nil unless first_point_time [Time.at(first_point_time), end_at.to_time] else # Get full range from user's points first_point_time = user.points.minimum(:timestamp) last_point_time = user.points.maximum(:timestamp) return nil unless first_point_time && last_point_time [Time.at(first_point_time), Time.at(last_point_time)] end end def create_chunk(chunk_start, chunk_end, global_start, global_end) # Calculate buffer zones, but don't exceed global boundaries buffer_start = [chunk_start - buffer_size, global_start].max buffer_end = [chunk_end + buffer_size, global_end].min { chunk_id: SecureRandom.uuid, start_timestamp: chunk_start.to_i, end_timestamp: chunk_end.to_i, buffer_start_timestamp: buffer_start.to_i, buffer_end_timestamp: buffer_end.to_i, start_time: chunk_start, end_time: chunk_end, buffer_start_time: buffer_start, buffer_end_time: buffer_end } end def chunk_has_points?(chunk) # Check if there are any points in the buffer range to avoid empty chunks user.points .where(timestamp: chunk[:buffer_start_timestamp]..chunk[:buffer_end_timestamp]) .exists? end end