mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-14 03:01:39 -05:00
Merge branch 'dev' into tests/system
This commit is contained in:
commit
89c286a69b
3 changed files with 91 additions and 75 deletions
32
Gemfile.lock
32
Gemfile.lock
|
|
@ -99,7 +99,7 @@ GEM
|
|||
bcrypt (3.1.20)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.9)
|
||||
bootsnap (1.18.4)
|
||||
bootsnap (1.18.6)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.2)
|
||||
racc
|
||||
|
|
@ -144,7 +144,7 @@ GEM
|
|||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.5.1)
|
||||
diff-lcs (1.6.2)
|
||||
docile (1.4.1)
|
||||
dotenv (3.1.7)
|
||||
dotenv-rails (3.1.7)
|
||||
|
|
@ -174,8 +174,8 @@ GEM
|
|||
gpx (1.2.0)
|
||||
nokogiri (~> 1.7)
|
||||
rake
|
||||
groupdate (6.5.1)
|
||||
activesupport (>= 7)
|
||||
groupdate (6.6.0)
|
||||
activesupport (>= 7.1)
|
||||
hashdiff (1.1.2)
|
||||
httparty (0.23.1)
|
||||
csv
|
||||
|
|
@ -232,7 +232,7 @@ GEM
|
|||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (5.25.5)
|
||||
msgpack (1.7.3)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.7.1)
|
||||
bigdecimal (~> 3.1)
|
||||
|
|
@ -259,7 +259,7 @@ GEM
|
|||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.9)
|
||||
oj (3.16.10)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
optimist (3.2.0)
|
||||
|
|
@ -360,21 +360,21 @@ GEM
|
|||
rgeo (>= 1.0.0)
|
||||
rspec-core (3.13.3)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.3)
|
||||
rspec-expectations (3.13.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.2)
|
||||
rspec-mocks (3.13.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (7.1.1)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
rspec-rails (8.0.0)
|
||||
actionpack (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
rspec-core (~> 3.13)
|
||||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-support (3.13.2)
|
||||
rspec-support (3.13.3)
|
||||
rswag-api (2.16.0)
|
||||
activesupport (>= 5.2, < 8.1)
|
||||
railties (>= 5.2, < 8.1)
|
||||
|
|
@ -415,10 +415,10 @@ GEM
|
|||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
sentry-rails (5.23.0)
|
||||
sentry-rails (5.24.0)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.23.0)
|
||||
sentry-ruby (5.23.0)
|
||||
sentry-ruby (~> 5.24.0)
|
||||
sentry-ruby (5.24.0)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
shoulda-matchers (6.5.0)
|
||||
|
|
|
|||
|
|
@ -12,43 +12,64 @@ class CountriesAndCities
|
|||
points
|
||||
.reject { |point| point.country.nil? || point.city.nil? }
|
||||
.group_by(&:country)
|
||||
.transform_values { |country_points| process_country_points(country_points) }
|
||||
.map { |country, cities| CountryData.new(country: country, cities: cities) }
|
||||
.map do |country, country_points|
|
||||
cities = process_country_points(country_points)
|
||||
CountryData.new(country: country, cities: cities) if cities.any?
|
||||
end.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :points
|
||||
|
||||
# Step 1: Process points to group by consecutive cities and time
|
||||
def group_points_with_consecutive_cities(country_points)
|
||||
sorted_points = country_points.sort_by(&:timestamp)
|
||||
|
||||
sessions = []
|
||||
current_session = []
|
||||
|
||||
sorted_points.each_with_index do |point, index|
|
||||
if current_session.empty?
|
||||
current_session << point
|
||||
next
|
||||
end
|
||||
|
||||
prev_point = sorted_points[index - 1]
|
||||
|
||||
# Split session if city changes or time gap exceeds the threshold
|
||||
if point.city != prev_point.city
|
||||
sessions << current_session
|
||||
current_session = []
|
||||
end
|
||||
|
||||
current_session << point
|
||||
end
|
||||
|
||||
sessions << current_session unless current_session.empty?
|
||||
sessions
|
||||
end
|
||||
|
||||
# Step 2: Filter sessions that don't meet the minimum minutes per city
|
||||
def filter_sessions(sessions)
|
||||
sessions.map do |session|
|
||||
end_time = session.last.timestamp
|
||||
duration = (end_time - session.first.timestamp) / 60 # Convert seconds to minutes
|
||||
|
||||
if duration >= MIN_MINUTES_SPENT_IN_CITY
|
||||
CityData.new(
|
||||
city: session.first.city,
|
||||
points: session.size,
|
||||
timestamp: end_time,
|
||||
stayed_for: duration
|
||||
)
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Process points for each country
|
||||
def process_country_points(country_points)
|
||||
country_points
|
||||
.group_by(&:city)
|
||||
.transform_values { |city_points| create_city_data_if_valid(city_points) }
|
||||
.values
|
||||
.compact
|
||||
end
|
||||
|
||||
def create_city_data_if_valid(city_points)
|
||||
timestamps = city_points.pluck(:timestamp)
|
||||
duration = calculate_duration_in_minutes(timestamps)
|
||||
city = city_points.first.city
|
||||
points_count = city_points.size
|
||||
|
||||
build_city_data(city, points_count, timestamps, duration)
|
||||
end
|
||||
|
||||
def build_city_data(city, points_count, timestamps, duration)
|
||||
return nil if duration < ::MIN_MINUTES_SPENT_IN_CITY
|
||||
|
||||
CityData.new(
|
||||
city: city,
|
||||
points: points_count,
|
||||
timestamp: timestamps.max,
|
||||
stayed_for: duration
|
||||
)
|
||||
end
|
||||
|
||||
def calculate_duration_in_minutes(timestamps)
|
||||
((timestamps.max - timestamps.min).to_i / 60)
|
||||
sessions = group_points_with_consecutive_cities(country_points)
|
||||
filter_sessions(sessions)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,24 +6,29 @@ RSpec.describe CountriesAndCities do
|
|||
describe '#call' do
|
||||
subject(:countries_and_cities) { described_class.new(points).call }
|
||||
|
||||
# we have 5 points in the same city and country within 1 hour,
|
||||
# 5 points in the differnt city within 10 minutes
|
||||
# and we expect to get one country with one city which has 5 points
|
||||
# we have 15 points in the same city and different country within 2 hour,
|
||||
# 4 points in the differnt city within 10 minutes splitting the country
|
||||
# and we expect to get one country with one city which has 8 points
|
||||
|
||||
let(:timestamp) { DateTime.new(2021, 1, 1, 0, 0, 0) }
|
||||
|
||||
let(:points) do
|
||||
[
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp:),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 10.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 20.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 30.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 40.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 50.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 60.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 70.minutes),
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 80.minutes),
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes)
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp:),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 10.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 20.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Germany', timestamp: timestamp + 30.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Germany', timestamp: timestamp + 40.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Germany', timestamp: timestamp + 50.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Germany', timestamp: timestamp + 60.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 70.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 80.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 90.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 100.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 110.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 120.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 130.minutes),
|
||||
create(:point, city: 'Kerpen', country: 'Belgium', timestamp: timestamp + 140.minutes)
|
||||
]
|
||||
end
|
||||
|
||||
|
|
@ -37,16 +42,12 @@ RSpec.describe CountriesAndCities do
|
|||
expect(countries_and_cities).to eq(
|
||||
[
|
||||
CountriesAndCities::CountryData.new(
|
||||
country: 'Germany',
|
||||
country: 'Belgium',
|
||||
cities: [
|
||||
CountriesAndCities::CityData.new(
|
||||
city: 'Berlin', points: 8, timestamp: 1_609_463_400, stayed_for: 70
|
||||
city: 'Kerpen', points: 8, timestamp: 1_609_467_600, stayed_for: 70
|
||||
)
|
||||
]
|
||||
),
|
||||
CountriesAndCities::CountryData.new(
|
||||
country: 'Belgium',
|
||||
cities: []
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -60,21 +61,15 @@ RSpec.describe CountriesAndCities do
|
|||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 10.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 20.minutes),
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 80.minutes),
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes)
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes),
|
||||
create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 100.minutes),
|
||||
create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 110.minutes)
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns countries and cities' do
|
||||
expect(countries_and_cities).to eq(
|
||||
[
|
||||
CountriesAndCities::CountryData.new(
|
||||
country: 'Germany',
|
||||
cities: []
|
||||
),
|
||||
CountriesAndCities::CountryData.new(
|
||||
country: 'Belgium',
|
||||
cities: []
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue