eligible 2.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.
@@ -0,0 +1,20 @@
1
+ module Eligible
2
+ class EligibleError < StandardError
3
+ attr_reader :message
4
+ attr_reader :http_status
5
+ attr_reader :http_body
6
+ attr_reader :json_body
7
+
8
+ def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
9
+ @message = message
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @json_body = json_body
13
+ end
14
+
15
+ def to_s
16
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
17
+ "#{status_string}#{@message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Eligible
2
+ module JSON
3
+ if MultiJson.respond_to?(:dump)
4
+ def self.dump(*args)
5
+ MultiJson.dump(*args)
6
+ end
7
+
8
+ def self.load(*args)
9
+ MultiJson.load(*args)
10
+ end
11
+ else
12
+ def self.dump(*args)
13
+ MultiJson.encode(*args)
14
+ end
15
+
16
+ def self.load(*args)
17
+ MultiJson.decode(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ module Eligible
2
+ COMMON_ATTRIBUTES = [:timestamp, :eligible_id, :mapping_version, :primary_insurance, :additional_insurance]
3
+ STATUS_ATTRIBUTES = [:type, :coverage_status]
4
+ BALANCE_ATTRIBUTES = [:balance, :comments]
5
+ STOP_LOSS_ATTRIBUTES = [:stop_loss_in_network, :stop_loss_out_network]
6
+
7
+ class Plan < APIResource
8
+ def self.get(params, api_key=nil)
9
+ response, api_key = Eligible.request(:get, url, api_key, params)
10
+ Util.convert_to_eligible_object(response, api_key)
11
+ end
12
+
13
+ def all
14
+ error ? nil : to_hash
15
+ end
16
+
17
+ def status
18
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES
19
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
20
+ end
21
+
22
+ def deductible
23
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES + [:deductible_in_network, :deductible_out_network]
24
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
25
+ end
26
+
27
+ def dates
28
+ keys = COMMON_ATTRIBUTES
29
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
30
+ end
31
+
32
+ def balance
33
+ keys = COMMON_ATTRIBUTES + BALANCE_ATTRIBUTES
34
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
35
+ end
36
+
37
+ def stop_loss
38
+ keys = COMMON_ATTRIBUTES + STOP_LOSS_ATTRIBUTES
39
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ module Eligible
2
+ class Service < APIResource
3
+ COMMON_ATTRIBUTES = [:timestamp, :eligible_id, :mapping_version, :additional_insurance]
4
+ STATUS_ATTRIBUTES = [:type, :coverage_status]
5
+ VISITS_ATTRIBUTES = [:not_covered, :comments, :precertification_needed, :visits_in_network, :visits_out_network]
6
+ COPAYMENT_ATTRIBUTES = [:copayment_in_network, :copayment_out_network]
7
+ COINSURANCE_ATTRIBUTES = [:coinsurance_in_network, :coinsurance_out_network]
8
+ DEDUCTIBLE_ATTRIBUTES = [:deductible_in_network, :deductible_out_network]
9
+
10
+ def self.get(params, api_key=nil)
11
+ response, api_key = Eligible.request(:get, url, api_key, params)
12
+ Util.convert_to_eligible_object(response, api_key)
13
+ end
14
+
15
+ def all
16
+ error ? nil : to_hash
17
+ end
18
+
19
+ def visits
20
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES + VISITS_ATTRIBUTES
21
+ k_to_hash(keys)
22
+ end
23
+
24
+ def copayment
25
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES + COPAYMENT_ATTRIBUTES
26
+ k_to_hash(keys)
27
+ end
28
+
29
+ def coinsurance
30
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES + COINSURANCE_ATTRIBUTES
31
+ k_to_hash(keys)
32
+ end
33
+
34
+ def deductible
35
+ keys = COMMON_ATTRIBUTES + STATUS_ATTRIBUTES + DEDUCTIBLE_ATTRIBUTES
36
+ k_to_hash(keys)
37
+ end
38
+
39
+ def self.general(params, api_key=nil)
40
+ response, api_key = Eligible.request(:get, "/service/general.json", api_key, params)
41
+ response = Util.convert_to_eligible_object(response, api_key)
42
+ response = response.to_hash if response.is_a? Hash
43
+ response
44
+ end
45
+
46
+ def self.list(params, api_key=nil)
47
+ response, api_key = Eligible.request(:get, "/service/list.json", api_key, params)
48
+ response = Util.convert_to_eligible_object(response, api_key)
49
+ response = response.to_hash if response.is_a? Hash
50
+ response
51
+ end
52
+
53
+ private
54
+
55
+ def k_to_hash(keys)
56
+ error ? nil : to_hash.select { |k, v| keys.include?(k) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,109 @@
1
+ module Eligible
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.convert_to_eligible_object(resp, api_key)
19
+ types = {
20
+ 'plan' => Plan,
21
+ 'service' => Service,
22
+ 'demographic' => Demographic,
23
+ 'claim' => Claim,
24
+ 'coverage' => Coverage,
25
+ 'enrollment' => Enrollment
26
+ }
27
+ case resp
28
+ when Array
29
+ if resp[0] && resp[0][:enrollment_npi]
30
+ Enrollment.construct_from({ :enrollments => resp }, api_key)
31
+ else
32
+ resp.map { |i| convert_to_eligible_object(i, api_key) }
33
+ end
34
+ when Hash
35
+ # Try converting to a known object class. If none available, fall back to generic APIResource
36
+ if resp[:mapping_version] && klass_name = resp[:mapping_version].match(/^[^\/]*/)[0]
37
+ klass = types[klass_name]
38
+ elsif resp[:enrollment_request]
39
+ klass = Enrollment
40
+ elsif resp[:demographics]
41
+ klass = Coverage
42
+ end
43
+ klass ||= EligibleObject
44
+ klass.construct_from(resp, api_key)
45
+ else
46
+ resp
47
+ end
48
+ end
49
+
50
+ def self.file_readable(file)
51
+ begin
52
+ File.open(file) { |f| }
53
+ rescue
54
+ false
55
+ else
56
+ true
57
+ end
58
+ end
59
+
60
+ def self.symbolize_names(object)
61
+ case object
62
+ when Hash
63
+ new = {}
64
+ object.each do |key, value|
65
+ key = (key.to_sym rescue key) || key
66
+ new[key] = symbolize_names(value)
67
+ end
68
+ new
69
+ when Array
70
+ object.map { |value| symbolize_names(value) }
71
+ else
72
+ object
73
+ end
74
+ end
75
+
76
+ def self.url_encode(key)
77
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
78
+ end
79
+
80
+ def self.flatten_params(params, parent_key=nil)
81
+ result = []
82
+ params.each do |key, value|
83
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
84
+ if value.is_a?(Hash)
85
+ result += flatten_params(value, calculated_key)
86
+ elsif value.is_a?(Array)
87
+ result += flatten_params_array(value, calculated_key)
88
+ else
89
+ result << [calculated_key, value]
90
+ end
91
+ end
92
+ result
93
+ end
94
+
95
+ def self.flatten_params_array(value, calculated_key)
96
+ result = []
97
+ value.each do |elem|
98
+ if elem.is_a?(Hash)
99
+ result += flatten_params(elem, calculated_key)
100
+ elsif elem.is_a?(Array)
101
+ result += flatten_params_array(elem, calculated_key)
102
+ else
103
+ result << ["#{calculated_key}[]", elem]
104
+ end
105
+ end
106
+ result
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module Eligible
2
+ VERSION = "2.0"
3
+ end
@@ -0,0 +1,16 @@
1
+ module Eligible
2
+ class Webhook < APIResource
3
+
4
+ def self.all(api_key=nil)
5
+ response, api_key = Eligible.request(:get, url, api_key, params)
6
+ Util.convert_to_eligible_object(response, api_key)
7
+ end
8
+
9
+ def self.post(params, api_key=nil)
10
+ response, api_key = Eligible.request(:get, url, api_key, params)
11
+ Util.convert_to_eligible_object(response, api_key)
12
+ end
13
+
14
+ end
15
+
16
+ end
data/lib/eligible.rb ADDED
@@ -0,0 +1,218 @@
1
+ require 'cgi'
2
+ require 'set'
3
+ require 'rubygems'
4
+ require 'openssl'
5
+ require 'json'
6
+
7
+ gem 'rest-client', '~> 1.4'
8
+ require 'rest_client'
9
+ require 'multi_json'
10
+
11
+ require 'eligible/version'
12
+ require 'eligible/util'
13
+ require 'eligible/json'
14
+ require 'eligible/eligible_object'
15
+ require 'eligible/api_resource'
16
+ require 'eligible/plan'
17
+ require 'eligible/service'
18
+ require 'eligible/demographic'
19
+ require 'eligible/claim'
20
+ require 'eligible/enrollment'
21
+ require 'eligible/coverage'
22
+
23
+ # Errors
24
+ require 'eligible/errors/eligible_error'
25
+ require 'eligible/errors/api_connection_error'
26
+ require 'eligible/errors/authentication_error'
27
+ require 'eligible/errors/api_error'
28
+
29
+ module Eligible
30
+ @@api_key = nil
31
+ @@api_base = "https://gds.eligibleapi.com/v1.1"
32
+ @@api_version = 1.1
33
+
34
+ def self.api_url(url='')
35
+ @@api_base + url
36
+ end
37
+
38
+ def self.api_key
39
+ @@api_key
40
+ end
41
+
42
+ def self.api_key=(api_key)
43
+ @@api_key = api_key
44
+ end
45
+
46
+ def self.api_version=(version)
47
+ @@api_version = version
48
+ end
49
+
50
+ def self.api_version
51
+ @@api_version
52
+ end
53
+
54
+ def self.request(method, url, api_key, params={}, headers={})
55
+ api_key ||= @@api_key
56
+ raise AuthenticationError.new('No API key provided. (HINT: set your API key using "Eligible.api_key = <API-KEY>".') unless api_key
57
+
58
+ # if !verify_ssl_certs
59
+ # unless @no_verify
60
+ # $stderr.puts "WARNING: Running without SSL cert verification. Execute 'Eligible.verify_ssl_certs = true' to enable verification."
61
+ # @no_verify = true
62
+ # end
63
+ # ssl_opts = { :verify_ssl => false }
64
+ # elsif !Util.file_readable(@@ssl_bundle_path)
65
+ # unless @no_bundle
66
+ # $stderr.puts "WARNING: Running without SSL cert verification because #{@@ssl_bundle_path} isn't readable"
67
+ # @no_bundle = true
68
+ # end
69
+ # ssl_opts = { :verify_ssl => false }
70
+ # else
71
+ # ssl_opts = {
72
+ # :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
73
+ # :ssl_ca_file => @@ssl_bundle_path
74
+ # }
75
+ # end
76
+ uname = (@@uname ||= RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
77
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
78
+ ua = {
79
+ :bindings_version => Eligible::VERSION,
80
+ :lang => 'ruby',
81
+ :lang_version => lang_version,
82
+ :platform => RUBY_PLATFORM,
83
+ :publisher => 'eligible',
84
+ :uname => uname
85
+ }
86
+
87
+ # params = Util.objects_to_ids(params)
88
+ url = self.api_url(url)
89
+ case method.to_s.downcase.to_sym
90
+ when :get, :head, :delete
91
+ # Make params into GET parameters
92
+ url += "?api_key=#{api_key}"
93
+ if params && params.count > 0
94
+ query_string = Util.flatten_params(params).collect{|key, value| "#{key}=#{Util.url_encode(value)}"}.join('&')
95
+ url += "&#{query_string}"
96
+ end
97
+ payload = nil
98
+ else
99
+ payload = params.merge!({"api_key" => api_key }).to_json #Util.flatten_params(params).collect{|(key, value)| "#{key}=#{Util.url_encode(value)}"}.join('&')
100
+ end
101
+
102
+ begin
103
+ headers = { :x_eligible_client_user_agent => Eligible::JSON.dump(ua) }.merge(headers)
104
+ rescue => e
105
+ headers = {
106
+ :x_eligible_client_raw_user_agent => ua.inspect,
107
+ :error => "#{e} (#{e.class})"
108
+ }.merge(headers)
109
+ end
110
+
111
+ headers = {
112
+ :user_agent => "Eligible/v1 RubyBindings/#{Eligible::VERSION}",
113
+ :authorization => "Bearer #{api_key}",
114
+ :content_type => 'application/x-www-form-urlencoded'
115
+ }.merge(headers)
116
+
117
+ if self.api_version
118
+ headers[:eligible_version] = self.api_version
119
+ end
120
+
121
+ opts = {
122
+ :method => method,
123
+ :url => url,
124
+ :headers => headers,
125
+ :open_timeout => 30,
126
+ :payload => payload,
127
+ :timeout => 80
128
+ }#.merge(ssl_opts)
129
+
130
+ begin
131
+ response = execute_request(opts)
132
+ rescue SocketError => e
133
+ self.handle_restclient_error(e)
134
+ rescue NoMethodError => e
135
+ # Work around RestClient bug
136
+ if e.message =~ /\WRequestFailed\W/
137
+ e = APIConnectionError.new('Unexpected HTTP response code')
138
+ self.handle_restclient_error(e)
139
+ else
140
+ raise
141
+ end
142
+ rescue RestClient::ExceptionWithResponse => e
143
+ if rcode = e.http_code and rbody = e.http_body
144
+ self.handle_api_error(rcode, rbody)
145
+ else
146
+ self.handle_restclient_error(e)
147
+ end
148
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
149
+ self.handle_restclient_error(e)
150
+ end
151
+
152
+ rbody = response.body
153
+ rcode = response.code
154
+ begin
155
+ # Would use :symbolize_names => true, but apparently there is
156
+ # some library out there that makes symbolize_names not work.
157
+ resp = params[:format] && params[:format].match(/x12/i) ? rbody : Eligible::JSON.load(rbody)
158
+ rescue MultiJson::DecodeError
159
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
160
+ end
161
+
162
+ resp = Util.symbolize_names(resp)
163
+ [resp, api_key]
164
+ end
165
+
166
+ private
167
+
168
+ def self.execute_request(opts)
169
+ RestClient::Request.execute(opts)
170
+ end
171
+
172
+ def self.handle_api_error(rcode, rbody)
173
+ begin
174
+ error_obj = Eligible::JSON.load(rbody)
175
+ error_obj = Util.symbolize_names(error_obj)
176
+ error = error_obj[:error] or raise EligibleError.new # escape from parsing
177
+ rescue MultiJson::DecodeError, EligibleError
178
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
179
+ end
180
+
181
+ case rcode
182
+ when 400, 404 then
183
+ raise invalid_request_error(error, rcode, rbody, error_obj)
184
+ when 401
185
+ raise authentication_error(error, rcode, rbody, error_obj)
186
+ else
187
+ raise api_error(error, rcode, rbody, error_obj)
188
+ end
189
+ end
190
+
191
+ def self.invalid_request_error(error, rcode, rbody, error_obj)
192
+ InvalidRequestError.new(error[0][:message], error[:param], rcode, rbody, error_obj)
193
+ end
194
+
195
+ def self.authentication_error(error, rcode, rbody, error_obj)
196
+ AuthenticationError.new(error[0][:message], rcode, rbody, error_obj)
197
+ end
198
+
199
+ def self.api_error(error, rcode, rbody, error_obj)
200
+ APIError.new(error[0][:message], rcode, rbody, error_obj)
201
+ end
202
+
203
+ def self.handle_restclient_error(e)
204
+ case e
205
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
206
+ message = "Could not connect to Eligible (#{@@api_base}). Please check your internet connection and try again."
207
+ when RestClient::SSLCertificateNotVerified
208
+ message = "Could not verify Eligible's SSL certificate."
209
+ when SocketError
210
+ message = "Unexpected error communicating when trying to connect to Eligible."
211
+ else
212
+ message = "Unexpected error communicating with Eligible. If this problem persists, let us know at support@eligible.com."
213
+ end
214
+ message += "\n\n(Network error: #{e.message})"
215
+ raise APIConnectionError.new(message)
216
+ end
217
+
218
+ end