slcsp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/exe/slcsp ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ dir = File.dirname(File.expand_path('..', __FILE__))
4
+ require File.join(dir,'lib','slcsp','cli')
5
+
6
+ Slcsp::CLI.start
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
@@ -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
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slcsp
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "slcsp/version"
4
+ require "slcsp/config"
5
+ require "slcsp/index"
6
+ require "slcsp/zip_parser"
7
+ require "slcsp/plan_parser"
8
+
9
+ module Slcsp
10
+ class Error < StandardError; end
11
+ end
data/sig/slcsp.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Slcsp
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []