dawarich/app/services/tracks/cleaners/daily_cleaner.rb

116 lines
4 KiB
Ruby

# frozen_string_literal: true
# Track cleaning strategy for daily track processing.
#
# This cleaner handles tracks that overlap with the specified time window,
# ensuring proper handling of cross-day tracks and preventing orphaned points.
#
# How it works:
# 1. Finds tracks that overlap with the time window (not just those completely contained)
# 2. For overlapping tracks, removes only points within the time window
# 3. Deletes tracks that become empty after point removal
# 4. Preserves tracks that extend beyond the time window with their remaining points
#
# Key differences from ReplaceCleaner:
# - Handles tracks that span multiple days correctly
# - Uses overlap logic instead of containment logic
# - Preserves track portions outside the processing window
# - Prevents orphaned points from cross-day tracks
#
# Used primarily for:
# - Daily track processing that handles 24-hour windows
# - Incremental processing that respects existing cross-day tracks
# - Scenarios where tracks may span the processing boundary
#
# Example usage:
# cleaner = Tracks::Cleaners::DailyCleaner.new(user, start_at: 1.day.ago.beginning_of_day, end_at: 1.day.ago.end_of_day)
# cleaner.cleanup
#
module Tracks
module Cleaners
class DailyCleaner
attr_reader :user, :start_at, :end_at
def initialize(user, start_at: nil, end_at: nil)
@user = user
@start_at = start_at
@end_at = end_at
end
def cleanup
return unless start_at.present? && end_at.present?
overlapping_tracks = find_overlapping_tracks
return if overlapping_tracks.empty?
Rails.logger.info "Processing #{overlapping_tracks.count} overlapping tracks for user #{user.id} in time window #{start_at} to #{end_at}"
overlapping_tracks.each do |track|
process_overlapping_track(track)
end
end
private
def find_overlapping_tracks
# Find tracks that overlap with our time window
# A track overlaps if: track_start < window_end AND track_end > window_start
user.tracks.where(
'(start_at < ? AND end_at > ?)',
Time.zone.at(end_at),
Time.zone.at(start_at)
)
end
def process_overlapping_track(track)
# Find points within our time window that belong to this track
points_in_window = track.points.where(
'timestamp >= ? AND timestamp <= ?',
start_at.to_i,
end_at.to_i
)
if points_in_window.empty?
Rails.logger.debug "Track #{track.id} has no points in time window, skipping"
return
end
# Remove these points from the track
points_in_window.update_all(track_id: nil)
Rails.logger.debug "Removed #{points_in_window.count} points from track #{track.id}"
# Check if the track has any remaining points
remaining_points_count = track.points.count
if remaining_points_count == 0
# Track is now empty, delete it
Rails.logger.debug "Track #{track.id} is now empty, deleting"
track.destroy!
elsif remaining_points_count < 2
# Track has too few points to be valid, delete it and orphan remaining points
Rails.logger.debug "Track #{track.id} has insufficient points (#{remaining_points_count}), deleting"
track.points.update_all(track_id: nil)
track.destroy!
else
# Track still has valid points outside our window, update its boundaries
Rails.logger.debug "Track #{track.id} still has #{remaining_points_count} points, updating boundaries"
update_track_boundaries(track)
end
end
def update_track_boundaries(track)
remaining_points = track.points.order(:timestamp)
return if remaining_points.empty?
# Update track start/end times based on remaining points
track.update!(
start_at: Time.zone.at(remaining_points.first.timestamp),
end_at: Time.zone.at(remaining_points.last.timestamp)
)
end
end
end
end