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.
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: []