rate_center 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +102 -0
- data/Rakefile +12 -0
- data/data/cities/us/ak.yml +6420 -0
- data/data/cities/us/al.yml +10684 -0
- data/data/cities/us/ar.yml +11308 -0
- data/data/cities/us/az.yml +8396 -0
- data/data/cities/us/ca.yml +30626 -0
- data/data/cities/us/co.yml +9787 -0
- data/data/cities/us/ct.yml +3388 -0
- data/data/cities/us/dc.yml +12 -0
- data/data/cities/us/de.yml +1424 -0
- data/data/cities/us/fl.yml +17198 -0
- data/data/cities/us/ga.yml +12062 -0
- data/data/cities/us/hi.yml +2846 -0
- data/data/cities/us/ia.yml +18822 -0
- data/data/cities/us/id.yml +4324 -0
- data/data/cities/us/il.yml +27206 -0
- data/data/cities/us/in.yml +17594 -0
- data/data/cities/us/ks.yml +13626 -0
- data/data/cities/us/ky.yml +9958 -0
- data/data/cities/us/la.yml +8786 -0
- data/data/cities/us/ma.yml +2288 -0
- data/data/cities/us/md.yml +10738 -0
- data/data/cities/us/me.yml +1194 -0
- data/data/cities/us/mi.yml +13662 -0
- data/data/cities/us/mn.yml +16378 -0
- data/data/cities/us/mo.yml +21572 -0
- data/data/cities/us/ms.yml +7762 -0
- data/data/cities/us/mt.yml +9032 -0
- data/data/cities/us/nc.yml +14140 -0
- data/data/cities/us/nd.yml +7300 -0
- data/data/cities/us/ne.yml +10622 -0
- data/data/cities/us/nh.yml +740 -0
- data/data/cities/us/nj.yml +11848 -0
- data/data/cities/us/nm.yml +9352 -0
- data/data/cities/us/nv.yml +2652 -0
- data/data/cities/us/ny.yml +17946 -0
- data/data/cities/us/oh.yml +22704 -0
- data/data/cities/us/ok.yml +15154 -0
- data/data/cities/us/or.yml +7680 -0
- data/data/cities/us/pa.yml +32546 -0
- data/data/cities/us/pr.yml +5121 -0
- data/data/cities/us/ri.yml +560 -0
- data/data/cities/us/sc.yml +8768 -0
- data/data/cities/us/sd.yml +8768 -0
- data/data/cities/us/tn.yml +9404 -0
- data/data/cities/us/tx.yml +36996 -0
- data/data/cities/us/ut.yml +6308 -0
- data/data/cities/us/va.yml +12194 -0
- data/data/cities/us/vt.yml +1910 -0
- data/data/cities/us/wa.yml +11900 -0
- data/data/cities/us/wi.yml +14564 -0
- data/data/cities/us/wv.yml +7984 -0
- data/data/cities/us/wy.yml +3674 -0
- data/data/rate_centers/us/.keep +0 -0
- data/data/rate_centers/us/ak.yml +4584 -0
- data/data/rate_centers/us/al.yml +9798 -0
- data/data/rate_centers/us/ar.yml +15882 -0
- data/data/rate_centers/us/as.yml +119 -0
- data/data/rate_centers/us/az.yml +4159 -0
- data/data/rate_centers/us/ca.yml +21059 -0
- data/data/rate_centers/us/co.yml +6502 -0
- data/data/rate_centers/us/ct.yml +4086 -0
- data/data/rate_centers/us/dc.yml +16 -0
- data/data/rate_centers/us/de.yml +1133 -0
- data/data/rate_centers/us/fl.yml +10773 -0
- data/data/rate_centers/us/ga.yml +10143 -0
- data/data/rate_centers/us/gu.yml +218 -0
- data/data/rate_centers/us/hi.yml +1130 -0
- data/data/rate_centers/us/ia.yml +15438 -0
- data/data/rate_centers/us/id.yml +4357 -0
- data/data/rate_centers/us/il.yml +25322 -0
- data/data/rate_centers/us/in.yml +13924 -0
- data/data/rate_centers/us/ks.yml +12312 -0
- data/data/rate_centers/us/ky.yml +18629 -0
- data/data/rate_centers/us/la.yml +7785 -0
- data/data/rate_centers/us/ma.yml +7947 -0
- data/data/rate_centers/us/md.yml +6956 -0
- data/data/rate_centers/us/me.yml +9213 -0
- data/data/rate_centers/us/mi.yml +15653 -0
- data/data/rate_centers/us/mn.yml +15195 -0
- data/data/rate_centers/us/mo.yml +18238 -0
- data/data/rate_centers/us/mp.yml +29 -0
- data/data/rate_centers/us/ms.yml +7913 -0
- data/data/rate_centers/us/mt.yml +5659 -0
- data/data/rate_centers/us/nc.yml +11641 -0
- data/data/rate_centers/us/nd.yml +7341 -0
- data/data/rate_centers/us/ne.yml +8727 -0
- data/data/rate_centers/us/nh.yml +4499 -0
- data/data/rate_centers/us/nj.yml +13710 -0
- data/data/rate_centers/us/nm.yml +4636 -0
- data/data/rate_centers/us/nv.yml +2629 -0
- data/data/rate_centers/us/ny.yml +34576 -0
- data/data/rate_centers/us/oh.yml +20184 -0
- data/data/rate_centers/us/ok.yml +9970 -0
- data/data/rate_centers/us/or.yml +6867 -0
- data/data/rate_centers/us/pa.yml +40360 -0
- data/data/rate_centers/us/pr.yml +1899 -0
- data/data/rate_centers/us/ri.yml +1225 -0
- data/data/rate_centers/us/sc.yml +6251 -0
- data/data/rate_centers/us/sd.yml +6256 -0
- data/data/rate_centers/us/tn.yml +9289 -0
- data/data/rate_centers/us/tx.yml +27690 -0
- data/data/rate_centers/us/ut.yml +4712 -0
- data/data/rate_centers/us/va.yml +17679 -0
- data/data/rate_centers/us/vi.yml +146 -0
- data/data/rate_centers/us/vt.yml +4667 -0
- data/data/rate_centers/us/wa.yml +8151 -0
- data/data/rate_centers/us/wi.yml +12872 -0
- data/data/rate_centers/us/wv.yml +14818 -0
- data/data/rate_centers/us/wy.yml +2852 -0
- data/lib/rate_center/city.rb +33 -0
- data/lib/rate_center/collection.rb +23 -0
- data/lib/rate_center/data_loader.rb +75 -0
- data/lib/rate_center/data_prep.rb +121 -0
- data/lib/rate_center/data_source/local_calling_guide.rb +107 -0
- data/lib/rate_center/data_source/simple_maps.rb +93 -0
- data/lib/rate_center/distance.rb +3 -0
- data/lib/rate_center/errors.rb +6 -0
- data/lib/rate_center/rate_center.rb +34 -0
- data/lib/rate_center/vector.rb +3 -0
- data/lib/rate_center/version.rb +5 -0
- data/lib/rate_center.rb +21 -0
- data/release-please-config.json +15 -0
- metadata +400 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require "ostruct"
|
3
|
+
require_relative "collection"
|
4
|
+
require_relative "vector"
|
5
|
+
require_relative "distance"
|
6
|
+
|
7
|
+
module RateCenter
|
8
|
+
class City < OpenStruct
|
9
|
+
extend Collection
|
10
|
+
|
11
|
+
class << self
|
12
|
+
private
|
13
|
+
|
14
|
+
def data
|
15
|
+
::RateCenter.data_loader.cities
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_collection
|
19
|
+
data.map do |data|
|
20
|
+
city = new(**data)
|
21
|
+
city.nearby_rate_centers = Array(data["nearby_rate_centers"]).map do |rate_center|
|
22
|
+
distance = rate_center.fetch("distance")
|
23
|
+
Vector.new(
|
24
|
+
name: rate_center.fetch("name"),
|
25
|
+
distance: Distance.new(value: distance.fetch("value"), units: distance.fetch("units"))
|
26
|
+
)
|
27
|
+
end
|
28
|
+
city
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RateCenter
|
2
|
+
module Collection
|
3
|
+
def collection
|
4
|
+
raise Errors::DataNotLoadedError.new("No data loaded. Load data with RateCenter.load before calling this method") if data.nil?
|
5
|
+
|
6
|
+
@collection ||= load_collection
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
collection
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_by(attributes)
|
14
|
+
collection.find do |region|
|
15
|
+
attributes.all? { |key, value| region[key] == value }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_by!(*)
|
20
|
+
find_by(*) || raise(Errors::NotFoundError.new)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module RateCenter
|
4
|
+
class DataLoader
|
5
|
+
attr_reader :data_directory, :cities, :rate_centers
|
6
|
+
|
7
|
+
def initialize(**options)
|
8
|
+
@data_directory = options.fetch(:data_directory) { Pathname(File.expand_path("../../data/", __dir__)) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(type, ...)
|
12
|
+
case type.to_sym
|
13
|
+
when :all
|
14
|
+
@cities = load_data("cities", :all)
|
15
|
+
@rate_centers = load_data("rate_centers", :all)
|
16
|
+
when :cities
|
17
|
+
@cities = load_data("cities", ...)
|
18
|
+
when :rate_centers
|
19
|
+
@rate_centers = load_data("rate_centers", ...)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Invalid type: #{type}"
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load_data(type, all = nil, **options)
|
30
|
+
if all
|
31
|
+
load_data_from(data_directory.join(type).glob("**/*.yml"), type:)
|
32
|
+
else
|
33
|
+
load_data_with_filter(options.fetch(:only), type:)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_data_with_filter(filter, type:)
|
38
|
+
unless filter.is_a?(Hash)
|
39
|
+
return Array(filter).each_with_object([]) do |country, result|
|
40
|
+
result.concat(load_country_data(type:, country:))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
filter.each_with_object([]) do |(country, regions), result|
|
45
|
+
if regions.is_a?(Hash)
|
46
|
+
regions.each do |region, keys|
|
47
|
+
data = load_region_data(type:, country:, region:)
|
48
|
+
result.concat(data.select { |d| Array(keys).include?(d.fetch("name")) })
|
49
|
+
end
|
50
|
+
else
|
51
|
+
Array(regions).each do |region|
|
52
|
+
result.concat(load_region_data(type:, country:, region:))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_data_from(files, type:)
|
59
|
+
Array(files).each_with_object([]) do |file, result|
|
60
|
+
result.concat(YAML.load(file.read).fetch(type))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_country_data(type:, country:)
|
65
|
+
load_data_from(data_directory.join(type.to_s, country.to_s.downcase).glob("**/*.yml"), type:)
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_region_data(type:, country:, region:)
|
69
|
+
load_data_from(
|
70
|
+
data_directory.join(type.to_s, country.to_s.downcase, "#{region.to_s.downcase}.yml"),
|
71
|
+
type:
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "geocoder"
|
3
|
+
|
4
|
+
module RateCenter
|
5
|
+
class DataPrep
|
6
|
+
attr_reader :data_directory, :logger
|
7
|
+
|
8
|
+
def initialize(**options)
|
9
|
+
@data_directory = options.fetch(:data_directory)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
update_rate_centers_with_closest_city
|
14
|
+
update_cities_with_nearby_rate_centers
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def update_rate_centers_with_closest_city
|
20
|
+
rate_center_data.each do |region, rate_centers|
|
21
|
+
rate_centers.each do |rate_center|
|
22
|
+
next if rate_center["lat"].nil? || rate_center["long"].nil?
|
23
|
+
|
24
|
+
closest_city = find_closest(
|
25
|
+
lat: rate_center.fetch("lat"),
|
26
|
+
long: rate_center.fetch("long"),
|
27
|
+
data: city_data[region],
|
28
|
+
key: ->(data) { data.fetch("name") }
|
29
|
+
).first
|
30
|
+
|
31
|
+
next if closest_city.nil?
|
32
|
+
|
33
|
+
rate_center["closest_city"] = {
|
34
|
+
"name" => closest_city.name,
|
35
|
+
"distance" => {
|
36
|
+
"value" => closest_city.distance.round(2),
|
37
|
+
"units" => "km"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
write_data("rate_centers", "us", rate_center_data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_cities_with_nearby_rate_centers
|
47
|
+
city_data.each do |region, cities|
|
48
|
+
candidates = Array(rate_center_data[region]).reject { |data| data["lat"].nil? }
|
49
|
+
|
50
|
+
cities.each do |city|
|
51
|
+
candidate_distances = find_closest(
|
52
|
+
lat: city.fetch("lat"),
|
53
|
+
long: city.fetch("long"),
|
54
|
+
data: candidates,
|
55
|
+
key: ->(data) { data.fetch("name") }
|
56
|
+
)
|
57
|
+
|
58
|
+
candidates_by_distance = candidate_distances.each_with_object(Hash.new { |h, k| h[k] = [] }) do |rate_center, result|
|
59
|
+
result[rate_center.distance] << rate_center
|
60
|
+
end
|
61
|
+
|
62
|
+
nearby_rate_centers = candidates_by_distance.keys.first(3).each_with_object([]) do |distance, result|
|
63
|
+
result.concat(candidates_by_distance.fetch(distance))
|
64
|
+
end
|
65
|
+
|
66
|
+
city["nearby_rate_centers"] = nearby_rate_centers.map do |rate_center|
|
67
|
+
{
|
68
|
+
"name" => rate_center.name,
|
69
|
+
"distance" => {
|
70
|
+
"value" => rate_center.distance.round(2),
|
71
|
+
"units" => "km"
|
72
|
+
}
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
write_data("cities", "us", city_data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_closest(lat:, long:, data:, key:)
|
82
|
+
distance_to = Class.new(Struct.new(:name, :distance, keyword_init: true))
|
83
|
+
|
84
|
+
distances_to = Array(data).map do |d|
|
85
|
+
distance_to.new(
|
86
|
+
name: key.call(d),
|
87
|
+
distance: Geocoder::Calculations.distance_between(
|
88
|
+
[ lat, long ],
|
89
|
+
[ d.fetch("lat"), d.fetch("long") ],
|
90
|
+
units: :km
|
91
|
+
)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
distances_to.sort_by(&:distance)
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_data(type, country, data)
|
99
|
+
data.each do |state, state_data|
|
100
|
+
state_file = data_directory.join(type, country, "#{state.downcase}.yml")
|
101
|
+
state_file.write({ type => state_data }.to_yaml)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def city_data
|
106
|
+
@city_data ||= load_data("cities", "us")
|
107
|
+
end
|
108
|
+
|
109
|
+
def rate_center_data
|
110
|
+
@rate_center_data ||= load_data("rate_centers", "us")
|
111
|
+
end
|
112
|
+
|
113
|
+
def load_data(type, country)
|
114
|
+
data_directory.join(type, country).glob("**/*.yml").each_with_object({}) do |state_file, result|
|
115
|
+
data = YAML.load(state_file.read)
|
116
|
+
state = state_file.basename(".yml").to_s
|
117
|
+
result[state.upcase] = data.fetch(type.to_s)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "multi_xml"
|
3
|
+
require "rack"
|
4
|
+
require "yaml"
|
5
|
+
require "countries"
|
6
|
+
require "ostruct"
|
7
|
+
|
8
|
+
module RateCenter
|
9
|
+
module DataSource
|
10
|
+
class LocalCallingGuide
|
11
|
+
attr_reader :client, :data_directory
|
12
|
+
|
13
|
+
def initialize(**options)
|
14
|
+
@client = options.fetch(:client) { Client.new }
|
15
|
+
end
|
16
|
+
|
17
|
+
class ResponseParser
|
18
|
+
Response = Struct.new(:rate_centers, keyword_init: true)
|
19
|
+
|
20
|
+
attr_reader :parser
|
21
|
+
|
22
|
+
def initialize(**options)
|
23
|
+
@parser = options.fetch(:parser) { MultiXml }
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(xml)
|
27
|
+
data = parser.parse(xml)
|
28
|
+
rc_data = data.dig("root", "rcdata")
|
29
|
+
|
30
|
+
return Response.new(rate_centers: []) if rc_data.nil?
|
31
|
+
|
32
|
+
rc_data = rc_data.is_a?(Array) ? rc_data : Array([ rc_data ])
|
33
|
+
rate_centers = rc_data.map do |d|
|
34
|
+
OpenStruct.new(d.transform_values { |v| v.strip unless v.strip.empty? })
|
35
|
+
end
|
36
|
+
|
37
|
+
Response.new(rate_centers: rate_centers)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Client
|
42
|
+
HOST = "https://localcallingguide.com/".freeze
|
43
|
+
|
44
|
+
attr_reader :host, :http_client, :response_parser
|
45
|
+
|
46
|
+
def initialize(**options)
|
47
|
+
@host = options.fetch(:host, HOST)
|
48
|
+
@http_client = options.fetch(:http_client) { default_http_client }
|
49
|
+
@response_parser = options.fetch(:response_parser) { ResponseParser.new }
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_rate_center_data(params)
|
53
|
+
uri = URI("/xmlrc.php")
|
54
|
+
uri.query = Rack::Utils.build_query(params)
|
55
|
+
response = http_client.get(uri)
|
56
|
+
response_parser.parse(response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def default_http_client
|
62
|
+
Faraday.new(url: host) do |builder|
|
63
|
+
builder.headers["Accept"] = "application/xml"
|
64
|
+
builder.headers["Content-Type"] = "application/xml"
|
65
|
+
|
66
|
+
builder.response :raise_error
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_data!(**options)
|
72
|
+
data_directory = options.fetch(:data_directory)
|
73
|
+
FileUtils.mkdir_p(data_directory)
|
74
|
+
|
75
|
+
us_regions = Array(regions_for("US"))
|
76
|
+
|
77
|
+
Array(us_regions).each do |region, _|
|
78
|
+
data_file = data_directory.join("#{region.downcase}.yml")
|
79
|
+
rate_centers = client.fetch_rate_center_data(region:).rate_centers
|
80
|
+
next if rate_centers.empty?
|
81
|
+
|
82
|
+
data = rate_centers.sort_by(&:rc).map do |rate_center|
|
83
|
+
{
|
84
|
+
"country" => "US",
|
85
|
+
"region" => region,
|
86
|
+
"exchange" => rate_center.exch,
|
87
|
+
"name" => rate_center.rcshort || rate_center.rc,
|
88
|
+
"full_name" => rate_center.rc,
|
89
|
+
"lata" => rate_center.lata,
|
90
|
+
"ilec_name" => rate_center.ilec_name,
|
91
|
+
"lat" => rate_center.rc_lat,
|
92
|
+
"long" => rate_center.rc_lon
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
data_file.write({ "rate_centers" => data }.to_yaml)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def regions_for(country_code)
|
103
|
+
ISO3166::Country.new(country_code).subdivisions
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "zip"
|
3
|
+
require "csv"
|
4
|
+
require "yaml"
|
5
|
+
require "ostruct"
|
6
|
+
|
7
|
+
module RateCenter
|
8
|
+
module DataSource
|
9
|
+
class SimpleMaps
|
10
|
+
class ResponseParser
|
11
|
+
DATABASE_FILENAME = "uscities.csv".freeze
|
12
|
+
|
13
|
+
def parse(response)
|
14
|
+
database_file = extract_zipped_file(response, filename: DATABASE_FILENAME)
|
15
|
+
|
16
|
+
CSV.parse(database_file.read, headers: true).each_with_object([]) do |row_data, result|
|
17
|
+
result << OpenStruct.new(row_data.to_h)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def extract_zipped_file(zip_data, filename:)
|
24
|
+
Zip::InputStream.open(StringIO.new(zip_data)) do |zip_stream|
|
25
|
+
while (entry = zip_stream.get_next_entry)
|
26
|
+
return entry.get_input_stream if entry.name == filename
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Client
|
33
|
+
HOST = "https://simplemaps.com/".freeze
|
34
|
+
DEFAULT_DOWNLOAD_PATH = "static/data/us-cities/1.79/basic/simplemaps_uscities_basicv1.79.zip"
|
35
|
+
|
36
|
+
attr_reader :host, :http_client, :response_parser
|
37
|
+
|
38
|
+
def initialize(**options)
|
39
|
+
@host = options.fetch(:host, HOST)
|
40
|
+
@http_client = options.fetch(:http_client) { default_http_client }
|
41
|
+
@response_parser = options.fetch(:response_parser) { ResponseParser.new }
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_data(**options)
|
45
|
+
uri = URI(options.fetch(:path, DEFAULT_DOWNLOAD_PATH))
|
46
|
+
response = http_client.get(uri)
|
47
|
+
response_parser.parse(response.body)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def default_http_client
|
53
|
+
Faraday.new(url: host) do |builder|
|
54
|
+
builder.response :raise_error
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :client, :data_directory
|
60
|
+
|
61
|
+
def initialize(**options)
|
62
|
+
@client = options.fetch(:client) { Client.new }
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_data!(**options)
|
66
|
+
data_directory = options.fetch(:data_directory)
|
67
|
+
FileUtils.mkdir_p(data_directory)
|
68
|
+
|
69
|
+
data = client.fetch_data
|
70
|
+
|
71
|
+
cities_by_state = data.each_with_object(Hash.new { |h, k| h[k] = [] }) do |city, result|
|
72
|
+
result[city.state_id] << city
|
73
|
+
end
|
74
|
+
|
75
|
+
cities_by_state.each do |state, cities|
|
76
|
+
data_file = data_directory.join("#{state.downcase}.yml")
|
77
|
+
|
78
|
+
data = cities.sort_by(&:city).map do |city|
|
79
|
+
{
|
80
|
+
"country" => "US",
|
81
|
+
"region" => city.state_id,
|
82
|
+
"name" => city.city,
|
83
|
+
"lat" => city.lat,
|
84
|
+
"long" => city.lng
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
data_file.write({ "cities" => data }.to_yaml)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require_relative "collection"
|
3
|
+
require_relative "vector"
|
4
|
+
require_relative "distance"
|
5
|
+
|
6
|
+
module RateCenter
|
7
|
+
class RateCenter < OpenStruct
|
8
|
+
extend Collection
|
9
|
+
|
10
|
+
class << self
|
11
|
+
private
|
12
|
+
|
13
|
+
def data
|
14
|
+
::RateCenter.data_loader.rate_centers
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_collection
|
18
|
+
data.map do |data|
|
19
|
+
rate_center = new(**data)
|
20
|
+
closest_city = data["closest_city"]
|
21
|
+
next rate_center if closest_city.nil?
|
22
|
+
|
23
|
+
distance = closest_city.fetch("distance")
|
24
|
+
rate_center.closest_city = Vector.new(
|
25
|
+
name: closest_city.fetch("name"),
|
26
|
+
distance: Distance.new(value: distance.fetch("value"), units: distance.fetch("units"))
|
27
|
+
)
|
28
|
+
|
29
|
+
rate_center
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/rate_center.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module RateCenter
|
2
|
+
class << self
|
3
|
+
def load(...)
|
4
|
+
data_loader.load(...)
|
5
|
+
end
|
6
|
+
|
7
|
+
def unload
|
8
|
+
@data_loader = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def data_loader
|
12
|
+
@data_loader ||= DataLoader.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative "rate_center/errors"
|
18
|
+
require_relative "rate_center/data_loader"
|
19
|
+
require_relative "rate_center/city"
|
20
|
+
require_relative "rate_center/rate_center"
|
21
|
+
require_relative "rate_center/version"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"include-component-in-tag": false,
|
3
|
+
"version-file": "lib/rate_center/version.rb",
|
4
|
+
"packages": {
|
5
|
+
".": {
|
6
|
+
"changelog-path": "CHANGELOG.md",
|
7
|
+
"release-type": "ruby",
|
8
|
+
"bump-minor-pre-major": true,
|
9
|
+
"bump-patch-for-minor-pre-major": false,
|
10
|
+
"draft": false,
|
11
|
+
"prerelease": false
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
15
|
+
}
|