slcsp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +108 -0
- data/README.md +56 -0
- data/Rakefile +12 -0
- data/comments.md +20 -0
- data/data/plans.csv +22241 -0
- data/data/slcsp.csv +52 -0
- data/data/zips.csv +51542 -0
- data/exe/slcsp +6 -0
- data/lib/slcsp/cli.rb +65 -0
- data/lib/slcsp/config.rb +31 -0
- data/lib/slcsp/index.rb +25 -0
- data/lib/slcsp/plan_parser.rb +28 -0
- data/lib/slcsp/slcsp_matcher.rb +41 -0
- data/lib/slcsp/slcsp_parser.rb +29 -0
- data/lib/slcsp/version.rb +5 -0
- data/lib/slcsp/zip_parser.rb +26 -0
- data/lib/slcsp.rb +11 -0
- data/sig/slcsp.rbs +4 -0
- data/slcsp.gemspec +37 -0
- metadata +126 -0
data/exe/slcsp
ADDED
data/lib/slcsp/cli.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
Dir[File.join(File.dirname(__FILE__), '*.rb')].each { |file| require file }
|
6
|
+
|
7
|
+
module Slcsp
|
8
|
+
# cli client
|
9
|
+
class CLI < Thor
|
10
|
+
attr_accessor :zip_index, :plan_index
|
11
|
+
|
12
|
+
desc "configure",
|
13
|
+
"configures gem with the path of data files, metal level to search, output medium. \
|
14
|
+
TODO: add CLI options for configuring"
|
15
|
+
def configure
|
16
|
+
dir = File.dirname(File.expand_path('../..', __FILE__))
|
17
|
+
|
18
|
+
Slcsp::Config.configure do |config|
|
19
|
+
config.zips_file = File.join(dir, 'data', 'zips.csv')
|
20
|
+
say "configured zips file path to #{config.zips_file}"
|
21
|
+
config.plans_file = File.join(dir, 'data', 'plans.csv')
|
22
|
+
say "configured plans file path to #{config.plans_file}"
|
23
|
+
config.slcsp_file = File.join(dir, 'data', 'slcsp.csv')
|
24
|
+
say "configured slcsp file path to #{config.slcsp_file}"
|
25
|
+
config.output_medium = $stdout
|
26
|
+
say "configured output medium to be #{config.output_medium.inspect}"
|
27
|
+
config.target_level = 'Silver'
|
28
|
+
say "configured taregt level medium to be #{config.target_level}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "index",
|
33
|
+
"indexes data files for fast search later. \
|
34
|
+
TODO: add CLI options for indexing"
|
35
|
+
def index
|
36
|
+
zip_index = Slcsp::Index.new()
|
37
|
+
plan_index = Slcsp::Index.new()
|
38
|
+
zip_parser = Slcsp::ZipParser.new(Slcsp::Config.zips_file, zip_index)
|
39
|
+
plan_parser = Slcsp::PlanParser.new(Slcsp::Config.plans_file, plan_index)
|
40
|
+
|
41
|
+
say "Started indexing zips file..."
|
42
|
+
zip_parser.parse_and_record if zip_index.data.empty?
|
43
|
+
say "Completed indexing zips file. Total: #{zip_index.data.size} records."
|
44
|
+
|
45
|
+
say "Started indexing plans file..."
|
46
|
+
plan_parser.parse_and_record if plan_index.data.empty?
|
47
|
+
say "Completed indexing plans file. Total: #{plan_index.data.size} records."
|
48
|
+
|
49
|
+
return zip_index, plan_index
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "match",
|
53
|
+
"matches zip codes from slcsp.csv to SLCSP and prints to specified medium (STDOUT default)"
|
54
|
+
def match
|
55
|
+
@cli = Slcsp::CLI.new
|
56
|
+
|
57
|
+
@cli.invoke :configure
|
58
|
+
zip_index, plan_index = @cli.invoke :index
|
59
|
+
|
60
|
+
slcsp_parser = Slcsp::SlcspParser.new(Slcsp::Config.slcsp_file)
|
61
|
+
slcsp_matcher = Slcsp::SlcspMatcher.new(zip_index, plan_index, slcsp_parser)
|
62
|
+
slcsp_matcher.match_each
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/slcsp/config.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Slcsp
|
6
|
+
# Contains paths to data files: zips, plans, slcsp
|
7
|
+
class Config
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :zips_file, :plans_file, :slcsp_file, :target_level, :output_medium
|
12
|
+
|
13
|
+
def configure
|
14
|
+
yield self
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_indexes
|
18
|
+
@area_index = ZipParser.instance(zips_file)
|
19
|
+
@area_index = PlanParser.instance(plans_file)
|
20
|
+
end
|
21
|
+
|
22
|
+
def area_index
|
23
|
+
@area_index
|
24
|
+
end
|
25
|
+
|
26
|
+
def plan_index
|
27
|
+
@area_index
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/slcsp/index.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slcsp
|
4
|
+
# Sort of inverted index, in fact a simple hash for fast finds zip -> [rate_area, rate_area]
|
5
|
+
# e.g. {'23451' => ['VA 11', 'VA 12'], '75025' => ['TX 1', 'TX 2']}
|
6
|
+
class Index
|
7
|
+
def initialize(data={})
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :data
|
12
|
+
|
13
|
+
def get(key)
|
14
|
+
@data.key?(key) ? @data[key] : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def set(key, val)
|
18
|
+
if @data.key?(key)
|
19
|
+
@data[key] << val
|
20
|
+
else
|
21
|
+
@data[key] = [val]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
|
5
|
+
module Slcsp
|
6
|
+
# parses the csv with plans and save data to Index in the form
|
7
|
+
# e.g. {'VT 11' => [102.1, 192.3]} for the defined metal level
|
8
|
+
class PlanParser
|
9
|
+
KEY_FIELDS = %w[state rate_area].freeze
|
10
|
+
VALUE_FIELDS = %w[rate].freeze
|
11
|
+
|
12
|
+
def initialize(path, index)
|
13
|
+
@path = path
|
14
|
+
@index = index
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_and_record
|
18
|
+
File.open(@path) do |file|
|
19
|
+
CSV.foreach(file, headers: true) do |row|
|
20
|
+
next if row['metal_level'] && row['metal_level'] != Slcsp::Config.target_level
|
21
|
+
|
22
|
+
key, value = "#{row[KEY_FIELDS[0]]} #{row[KEY_FIELDS[1]]}", row[VALUE_FIELDS.join(' ')]
|
23
|
+
@index.set(key, value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
module Slcsp
|
6
|
+
# gets input from SlcspParse, finds rate_areas and SLCSP
|
7
|
+
class SlcspMatcher
|
8
|
+
def initialize(zip_index, plan_index, slcsp_parser)
|
9
|
+
@zip_index = zip_index
|
10
|
+
@plan_index = plan_index
|
11
|
+
@slcsp_parser = slcsp_parser
|
12
|
+
@output_medium = Slcsp::Config.output_medium
|
13
|
+
end
|
14
|
+
|
15
|
+
def match_each
|
16
|
+
@output_medium.print(@slcsp_parser.headers_line)
|
17
|
+
@slcsp_parser.parse_each do |row|
|
18
|
+
second = match(row)
|
19
|
+
@output_medium.print("#{row['zipcode']},#{format(second)}\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def match(row)
|
26
|
+
rate_areas = @zip_index.get(row['zipcode'])
|
27
|
+
plans = []
|
28
|
+
rate_areas.each do |rate_area|
|
29
|
+
temp_plans = @plan_index.get(rate_area)
|
30
|
+
plans += temp_plans if temp_plans
|
31
|
+
end
|
32
|
+
|
33
|
+
plans.size < 2 ? nil : plans.sort[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def format(number)
|
37
|
+
converted = number.to_f
|
38
|
+
converted.zero? ? nil : sprintf('%.2f', converted)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
|
5
|
+
module Slcsp
|
6
|
+
# parses the csv with slcsp zips and yeild line by line for external use
|
7
|
+
class SlcspParser
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_each(&block)
|
13
|
+
return unless block_given?
|
14
|
+
|
15
|
+
File.open(@path) do |file|
|
16
|
+
CSV.foreach(file, headers: true) do |row|
|
17
|
+
block.call row
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def headers_line
|
23
|
+
f = File.open(@path)
|
24
|
+
headers = f.gets
|
25
|
+
f.close
|
26
|
+
headers
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Slcsp
|
6
|
+
# parses the csv with zip and save data to Index in the form
|
7
|
+
# e.g. {'23451' => ['VA 11', 'VA 12'], '75025' => ['TX 1', 'TX 2']}
|
8
|
+
class ZipParser
|
9
|
+
KEY_FIELDS = %w[zipcode].freeze
|
10
|
+
VALUE_FIELDS = %w[state rate_area].freeze
|
11
|
+
|
12
|
+
def initialize(path, zip_index)
|
13
|
+
@path = path
|
14
|
+
@zip_index = zip_index
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_and_record
|
18
|
+
File.open(@path) do |file|
|
19
|
+
CSV.foreach(file, headers: true) do |row|
|
20
|
+
key, value = row[KEY_FIELDS.join(' ')], "#{row[VALUE_FIELDS[0]]} #{row[VALUE_FIELDS[1]]}"
|
21
|
+
@zip_index.set(key, value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/slcsp.rb
ADDED
data/sig/slcsp.rbs
ADDED
data/slcsp.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/slcsp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "slcsp"
|
7
|
+
spec.version = Slcsp::VERSION
|
8
|
+
spec.authors = ["Simon Bagreev"]
|
9
|
+
spec.email = ["sbagreev@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Ad Hoc home assignment"
|
12
|
+
spec.description = "Calculate the second lowest cost silver plan"
|
13
|
+
spec.homepage = "https://github.com/semmin/slcsp"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/semmin/slcsp"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/semmin/slcsp/Changelog.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "aruba"
|
34
|
+
spec.add_development_dependency "cucumber"
|
35
|
+
spec.add_development_dependency "pry", "~> 0.14"
|
36
|
+
spec.add_dependency "thor"
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slcsp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Bagreev
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aruba
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cucumber
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Calculate the second lowest cost silver plan
|
70
|
+
email:
|
71
|
+
- sbagreev@gmail.com
|
72
|
+
executables:
|
73
|
+
- slcsp
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".rspec"
|
78
|
+
- ".rubocop.yml"
|
79
|
+
- CHANGELOG.md
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- comments.md
|
85
|
+
- data/plans.csv
|
86
|
+
- data/slcsp.csv
|
87
|
+
- data/zips.csv
|
88
|
+
- exe/slcsp
|
89
|
+
- lib/slcsp.rb
|
90
|
+
- lib/slcsp/cli.rb
|
91
|
+
- lib/slcsp/config.rb
|
92
|
+
- lib/slcsp/index.rb
|
93
|
+
- lib/slcsp/plan_parser.rb
|
94
|
+
- lib/slcsp/slcsp_matcher.rb
|
95
|
+
- lib/slcsp/slcsp_parser.rb
|
96
|
+
- lib/slcsp/version.rb
|
97
|
+
- lib/slcsp/zip_parser.rb
|
98
|
+
- sig/slcsp.rbs
|
99
|
+
- slcsp.gemspec
|
100
|
+
homepage: https://github.com/semmin/slcsp
|
101
|
+
licenses: []
|
102
|
+
metadata:
|
103
|
+
allowed_push_host: https://rubygems.org
|
104
|
+
homepage_uri: https://github.com/semmin/slcsp
|
105
|
+
source_code_uri: https://github.com/semmin/slcsp
|
106
|
+
changelog_uri: https://github.com/semmin/slcsp/Changelog.md
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 2.6.0
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.3.7
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Ad Hoc home assignment
|
126
|
+
test_files: []
|