dawarich/app/services/visits/calculate.rb

92 lines
2.3 KiB
Ruby

# frozen_string_literal: true
class Visits::Calculate
def initialize(points)
@points = points
end
def call
# Only one visit per city per day
normalized_visits.flat_map do |country|
{
country: country[:country],
cities: country[:cities].uniq { [_1[:city], Time.zone.at(_1[:timestamp]).to_date] }
}
end
end
def normalized_visits
normalize_result(city_visits)
end
private
attr_reader :points
def group_points
points.sort_by(&:timestamp).reject { _1.city.nil? }.group_by(&:country)
end
def city_visits
group_points.transform_values do |grouped_points|
grouped_points
.group_by(&:city)
.transform_values { |city_points| identify_consecutive_visits(city_points) }
end
end
def identify_consecutive_visits(city_points)
visits = []
current_visit = []
city_points.each_cons(2) do |point1, point2|
time_diff = (point2.timestamp - point1.timestamp) / 60
if time_diff <= MIN_MINUTES_SPENT_IN_CITY
current_visit << point1 unless current_visit.include?(point1)
current_visit << point2
else
visits << create_visit(current_visit) if current_visit.size > 1
current_visit = []
end
end
visits << create_visit(current_visit) if current_visit.size > 1
visits
end
def create_visit(points)
{
city: points.first.city,
points:,
stayed_for: calculate_stayed_time(points),
last_timestamp: points.last.timestamp
}
end
def calculate_stayed_time(points)
return 0 if points.empty?
min_time = points.first.timestamp
max_time = points.last.timestamp
((max_time - min_time) / 60).round
end
def normalize_result(hash)
hash.map do |country, cities|
{
country:,
cities: cities.values.flatten
.select { |visit| visit[:stayed_for] >= MIN_MINUTES_SPENT_IN_CITY }
.map do |visit|
{
city: visit[:city],
points: visit[:points].count,
timestamp: visit[:last_timestamp],
stayed_for: visit[:stayed_for]
}
end
}
end.reject { |entry| entry[:cities].empty? }
end
end