piplapis-ruby 5.0.4 → 5.0.5
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 +4 -4
- data/LICENSE +10 -10
- data/README.md +25 -25
- data/lib/pipl.rb +35 -35
- data/lib/pipl/client.rb +177 -177
- data/lib/pipl/configurable.rb +58 -58
- data/lib/pipl/consts.rb +76 -76
- data/lib/pipl/containers.rb +294 -294
- data/lib/pipl/default.rb +66 -67
- data/lib/pipl/errors.rb +61 -61
- data/lib/pipl/fields.rb +732 -732
- data/lib/pipl/response.rb +220 -220
- data/lib/pipl/utils.rb +68 -68
- data/lib/pipl/version.rb +2 -2
- data/pipl.gemspec +18 -18
- metadata +2 -2
data/lib/pipl/default.rb
CHANGED
@@ -1,67 +1,66 @@
|
|
1
|
-
require_relative 'version'
|
2
|
-
|
3
|
-
module Pipl
|
4
|
-
|
5
|
-
module Default
|
6
|
-
|
7
|
-
API_ENDPOINT = 'https://api.pipl.com/search/'.freeze
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
module Pipl
|
4
|
+
|
5
|
+
module Default
|
6
|
+
|
7
|
+
API_ENDPOINT = 'https://api.pipl.com/search/'.freeze
|
8
|
+
USER_AGENT = "piplapis/ruby/#{Pipl::VERSION}".freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def options
|
13
|
+
Hash[Pipl::Configurable.keys.map{|key| [key, send(key)]}]
|
14
|
+
end
|
15
|
+
|
16
|
+
def api_key
|
17
|
+
ENV['PIPL_API_KEY']
|
18
|
+
end
|
19
|
+
|
20
|
+
def minimum_probability
|
21
|
+
ENV['PIPL_MINIMUM_PROBABILITY']
|
22
|
+
end
|
23
|
+
|
24
|
+
def minimum_match
|
25
|
+
ENV['PIPL_MINIMUM_MATCH']
|
26
|
+
end
|
27
|
+
|
28
|
+
def hide_sponsored
|
29
|
+
ENV['PIPL_HIDE_SPONSORED']
|
30
|
+
end
|
31
|
+
|
32
|
+
def live_feeds
|
33
|
+
ENV['PIPL_LIVE_FEEDS']
|
34
|
+
end
|
35
|
+
|
36
|
+
def show_sources
|
37
|
+
ENV['PIPL_SHOW_SOURCES']
|
38
|
+
end
|
39
|
+
|
40
|
+
def match_requirements
|
41
|
+
ENV['PIPL_MATCH_REQUIREMENTS']
|
42
|
+
end
|
43
|
+
|
44
|
+
def source_category_requirements
|
45
|
+
ENV['PIPL_SOURCE_CATEGORY_REQUIREMENTS']
|
46
|
+
end
|
47
|
+
|
48
|
+
def infer_persons
|
49
|
+
ENV['PIPL_INFER_PERSONS']
|
50
|
+
end
|
51
|
+
|
52
|
+
def strict_validation
|
53
|
+
ENV['PIPL_USER_STRICT_VALIDATION']
|
54
|
+
end
|
55
|
+
|
56
|
+
def api_endpoint
|
57
|
+
ENV.fetch 'PIPL_API_ENDPOINT', API_ENDPOINT
|
58
|
+
end
|
59
|
+
|
60
|
+
def user_agent
|
61
|
+
ENV.fetch 'PIPL_USER_AGENT', USER_AGENT
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/pipl/errors.rb
CHANGED
@@ -1,62 +1,62 @@
|
|
1
|
-
module Pipl
|
2
|
-
|
3
|
-
class AbstractMethodInvoked < StandardError;
|
4
|
-
end
|
5
|
-
|
6
|
-
class Client
|
7
|
-
|
8
|
-
class APIError < Exception
|
9
|
-
attr_reader :status_code
|
10
|
-
attr_reader :qps_allotted, :qps_current, :qps_live_allotted, :qps_live_current, :qps_demo_allotted,
|
11
|
-
:qps_demo_current, :quota_allotted, :quota_current, :quota_reset, :demo_usage_allotted,
|
12
|
-
:demo_usage_current, :demo_usage_expiry
|
13
|
-
|
14
|
-
def initialize(message, status_code, params={})
|
15
|
-
super message
|
16
|
-
@status_code = status_code
|
17
|
-
@qps_allotted = params[:qps_allotted]
|
18
|
-
@qps_current = params[:qps_current]
|
19
|
-
@qps_live_allotted = params[:qps_live_allotted]
|
20
|
-
@qps_live_current = params[:qps_live_current]
|
21
|
-
@qps_demo_allotted = params[:qps_demo_allotted]
|
22
|
-
@qps_demo_current = params[:qps_demo_current]
|
23
|
-
@quota_allotted = params[:quota_allotted]
|
24
|
-
@quota_current = params[:quota_current]
|
25
|
-
@quota_reset = params[:quota_reset]
|
26
|
-
@demo_usage_allotted = params[:demo_usage_allotted]
|
27
|
-
@demo_usage_current = params[:demo_usage_current]
|
28
|
-
@demo_usage_expiry = params[:demo_usage_expiry]
|
29
|
-
end
|
30
|
-
|
31
|
-
def is_user_error?
|
32
|
-
(400..499).member?(@status_code)
|
33
|
-
end
|
34
|
-
|
35
|
-
def is_pipl_error?
|
36
|
-
not is_user_error?
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.deserialize(json_str, headers={})
|
40
|
-
h = JSON.parse(json_str, symbolize_names: true)
|
41
|
-
params = Utils::extract_rate_limits(headers)
|
42
|
-
self.new(h[:error], h[:@http_status_code], params)
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.from_http_response(resp)
|
46
|
-
begin
|
47
|
-
self.deserialize(resp.body, resp)
|
48
|
-
rescue
|
49
|
-
Pipl::Client::APIError.new resp.message, resp.code
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Here for backward compatibility
|
54
|
-
def self.from_json(json_str)
|
55
|
-
self.deserialize(json_str)
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
1
|
+
module Pipl
|
2
|
+
|
3
|
+
class AbstractMethodInvoked < StandardError;
|
4
|
+
end
|
5
|
+
|
6
|
+
class Client
|
7
|
+
|
8
|
+
class APIError < Exception
|
9
|
+
attr_reader :status_code
|
10
|
+
attr_reader :qps_allotted, :qps_current, :qps_live_allotted, :qps_live_current, :qps_demo_allotted,
|
11
|
+
:qps_demo_current, :quota_allotted, :quota_current, :quota_reset, :demo_usage_allotted,
|
12
|
+
:demo_usage_current, :demo_usage_expiry
|
13
|
+
|
14
|
+
def initialize(message, status_code, params={})
|
15
|
+
super message
|
16
|
+
@status_code = status_code
|
17
|
+
@qps_allotted = params[:qps_allotted]
|
18
|
+
@qps_current = params[:qps_current]
|
19
|
+
@qps_live_allotted = params[:qps_live_allotted]
|
20
|
+
@qps_live_current = params[:qps_live_current]
|
21
|
+
@qps_demo_allotted = params[:qps_demo_allotted]
|
22
|
+
@qps_demo_current = params[:qps_demo_current]
|
23
|
+
@quota_allotted = params[:quota_allotted]
|
24
|
+
@quota_current = params[:quota_current]
|
25
|
+
@quota_reset = params[:quota_reset]
|
26
|
+
@demo_usage_allotted = params[:demo_usage_allotted]
|
27
|
+
@demo_usage_current = params[:demo_usage_current]
|
28
|
+
@demo_usage_expiry = params[:demo_usage_expiry]
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_user_error?
|
32
|
+
(400..499).member?(@status_code)
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_pipl_error?
|
36
|
+
not is_user_error?
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.deserialize(json_str, headers={})
|
40
|
+
h = JSON.parse(json_str, symbolize_names: true)
|
41
|
+
params = Utils::extract_rate_limits(headers)
|
42
|
+
self.new(h[:error], h[:@http_status_code], params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.from_http_response(resp)
|
46
|
+
begin
|
47
|
+
self.deserialize(resp.body, resp)
|
48
|
+
rescue
|
49
|
+
Pipl::Client::APIError.new resp.message, resp.code
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Here for backward compatibility
|
54
|
+
def self.from_json(json_str)
|
55
|
+
self.deserialize(json_str)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
62
|
end
|
data/lib/pipl/fields.rb
CHANGED
@@ -1,732 +1,732 @@
|
|
1
|
-
require 'date'
|
2
|
-
require 'set'
|
3
|
-
require_relative 'consts'
|
4
|
-
require_relative 'utils'
|
5
|
-
|
6
|
-
module Pipl
|
7
|
-
|
8
|
-
class Field
|
9
|
-
|
10
|
-
include Pipl::Utils
|
11
|
-
|
12
|
-
attr_accessor :valid_since, :last_seen, :inferred, :current
|
13
|
-
|
14
|
-
def initialize(params={})
|
15
|
-
@valid_since = params[:valid_since]
|
16
|
-
@last_seen = params[:last_seen]
|
17
|
-
@inferred = params[:inferred]
|
18
|
-
@current = params[:current]
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.from_hash(h)
|
22
|
-
params = base_params_from_hash h
|
23
|
-
extra_metadata.each { |p| params[p] = h["@#{p}".to_sym] }
|
24
|
-
params = h.merge params
|
25
|
-
self.new(params)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.base_params_from_hash(h)
|
29
|
-
params = {
|
30
|
-
inferred: h[:@inferred],
|
31
|
-
current: h[:@current],
|
32
|
-
type: h[:@type],
|
33
|
-
display: h[:display]
|
34
|
-
}
|
35
|
-
params[:valid_since] = Date.strptime(h[:@valid_since], Pipl::DATE_FORMAT) if h.key? :@valid_since
|
36
|
-
params[:last_seen] = Date.strptime(h[:@last_seen], Pipl::DATE_FORMAT) if h.key? :@last_seen
|
37
|
-
params[:date_range] = Pipl::DateRange.from_hash(h[:date_range]) if h.key? :date_range
|
38
|
-
params
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.extra_metadata
|
42
|
-
[]
|
43
|
-
end
|
44
|
-
|
45
|
-
def to_hash
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def is_searchable?
|
50
|
-
true
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
class Name < Field
|
57
|
-
# @!attribute first
|
58
|
-
# @return [String] First name
|
59
|
-
# @!attribute middle
|
60
|
-
# @return [String] Middle name or initial
|
61
|
-
# @!attribute last
|
62
|
-
# @return [String] Last name
|
63
|
-
# @!attribute prefix
|
64
|
-
# @return [String] Name prefix
|
65
|
-
# @!attribute suffix
|
66
|
-
# @return [String] Name suffix
|
67
|
-
# @!attribute type
|
68
|
-
# @return [String] Type of association of this name to a person. One of `present`, `maiden`, `former` or `alias`.
|
69
|
-
|
70
|
-
attr_accessor :first, :middle, :last, :prefix, :suffix, :type, :raw, :display
|
71
|
-
|
72
|
-
def initialize(params={})
|
73
|
-
super params
|
74
|
-
@first = params[:first]
|
75
|
-
@middle = params[:middle]
|
76
|
-
@last = params[:last]
|
77
|
-
@prefix = params[:prefix]
|
78
|
-
@suffix = params[:suffix]
|
79
|
-
@type = params[:type]
|
80
|
-
@raw = params[:raw]
|
81
|
-
@display = params[:display]
|
82
|
-
end
|
83
|
-
|
84
|
-
def to_hash
|
85
|
-
{first: @first, middle: @middle, last: @last, prefix: @prefix, suffix: @suffix, raw: @raw}
|
86
|
-
.reject { |_, value| value.nil? }
|
87
|
-
end
|
88
|
-
|
89
|
-
def is_searchable?
|
90
|
-
first = Pipl::Utils.alpha_chars(@first || '')
|
91
|
-
last = Pipl::Utils.alpha_chars(@last || '')
|
92
|
-
raw = Pipl::Utils.alpha_chars(@raw || '')
|
93
|
-
(first.length > 1 and last.length > 1) or raw.length > 3
|
94
|
-
end
|
95
|
-
|
96
|
-
def to_s
|
97
|
-
return @display if @display
|
98
|
-
|
99
|
-
vals = [@prefix, @first, @middle, @last, @suffix]
|
100
|
-
s = vals.any? ? vals.select { |v| v }.map { |v| v.capitalize }.join(' ') : nil
|
101
|
-
s ? Pipl::Utils.to_utf8(s) : ''
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
class Address < Field
|
108
|
-
# @!attribute country
|
109
|
-
# @return [String] 2 letters country code
|
110
|
-
# @!attribute state
|
111
|
-
# @return [String] 2 letters state code
|
112
|
-
# @!attribute city
|
113
|
-
# @return [String] City
|
114
|
-
# @!attribute street
|
115
|
-
# @return [String] Street
|
116
|
-
# @!attribute house
|
117
|
-
# @return [String] House
|
118
|
-
# @!attribute apartment
|
119
|
-
# @return [String] Apartment
|
120
|
-
# @!attribute zip_code
|
121
|
-
# @return [String] Zip Code
|
122
|
-
# @!attribute po_box
|
123
|
-
# @return [String] Post Office box number
|
124
|
-
# @!attribute type
|
125
|
-
# @return [String] Type of association of this address to a person. One of `home`, `work` or `old`.
|
126
|
-
# @!attribute raw
|
127
|
-
# @return [String] Unparsed address.
|
128
|
-
# @!attribute display
|
129
|
-
# @return [String] well formatted representation of this address for display purposes.
|
130
|
-
|
131
|
-
attr_accessor :country, :state, :city, :street, :house, :apartment, :zip_code, :po_box, :type, :raw, :display
|
132
|
-
|
133
|
-
def initialize(params={})
|
134
|
-
super params
|
135
|
-
@country = params[:country]
|
136
|
-
@state = params[:state]
|
137
|
-
@city = params[:city]
|
138
|
-
@street = params[:street]
|
139
|
-
@house = params[:house]
|
140
|
-
@apartment = params[:apartment]
|
141
|
-
@zip_code = params[:zip_code]
|
142
|
-
@po_box = params[:po_box]
|
143
|
-
@type = params[:type]
|
144
|
-
@raw = params[:raw]
|
145
|
-
@display = params[:display]
|
146
|
-
end
|
147
|
-
|
148
|
-
def is_valid_country?
|
149
|
-
@country and Pipl::COUNTRIES.key? @country.upcase.to_sym
|
150
|
-
end
|
151
|
-
|
152
|
-
def is_valid_state?
|
153
|
-
is_valid_country? and Pipl::STATES.key?(@country.upcase.to_sym) and
|
154
|
-
@state and Pipl::STATES[@country.upcase.to_sym].key?(@state.upcase.to_sym)
|
155
|
-
end
|
156
|
-
|
157
|
-
def to_hash
|
158
|
-
{country: @country, state: @state, city: @city, street: @street, house: @house, apartment: @apartment,
|
159
|
-
zip_code: @zip_code, po_box: @po_box, raw: @raw}
|
160
|
-
.reject { |_, value| value.nil? }
|
161
|
-
end
|
162
|
-
|
163
|
-
def is_searchable?
|
164
|
-
[@raw, @country, @state, @city].any? {|x| not x.to_s.empty?}
|
165
|
-
end
|
166
|
-
|
167
|
-
def is_sole_searchable?
|
168
|
-
not @raw.to_s.empty? or [@city, @street, @house].all? {|x| not x.to_s.empty?}
|
169
|
-
end
|
170
|
-
|
171
|
-
def country_full
|
172
|
-
Pipl::COUNTRIES[@country.upcase.to_sym] if @country
|
173
|
-
end
|
174
|
-
|
175
|
-
def state_full
|
176
|
-
Pipl::STATES[@country.upcase.to_sym][@state.upcase.to_sym] if is_valid_state?
|
177
|
-
end
|
178
|
-
|
179
|
-
def to_s
|
180
|
-
return @display if @display
|
181
|
-
|
182
|
-
country = @state ? @country : country_full
|
183
|
-
state = @city ? @state : state_full
|
184
|
-
vals = [@street, @city, state, country]
|
185
|
-
s = vals.any? ? vals.select { |v| v }.join(', ') : ''
|
186
|
-
|
187
|
-
if @street and (@house or @apartment)
|
188
|
-
prefix = [@house, @apartment].select { |v| v and not v.empty? }.join('-')
|
189
|
-
s = prefix + ' ' + (s || '')
|
190
|
-
end
|
191
|
-
|
192
|
-
if @po_box and @street.nil?
|
193
|
-
s = "P.O. Box #{@po_box} " + (s || '')
|
194
|
-
end
|
195
|
-
|
196
|
-
s ? Pipl::Utils.to_utf8(s) : ''
|
197
|
-
end
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
|
202
|
-
class Phone < Field
|
203
|
-
# @!attribute country_code
|
204
|
-
# @return [Fixnum] International country calling code
|
205
|
-
# @!attribute number
|
206
|
-
# @return [Fixnum] Actual phone number
|
207
|
-
# @!attribute extension
|
208
|
-
# @return [String] Office extension
|
209
|
-
# @!attribute type
|
210
|
-
# @return [String] Type of association of this phone to a person.
|
211
|
-
# Possible values are:
|
212
|
-
# mobile
|
213
|
-
# home_phone
|
214
|
-
# home_fax
|
215
|
-
# work_phone
|
216
|
-
# work_fax
|
217
|
-
# pager
|
218
|
-
# @!attribute raw
|
219
|
-
# @return [String] Unparsed phone number
|
220
|
-
# @!attribute display
|
221
|
-
# @return [String] Well formatted representation of this phone number for display purposes.
|
222
|
-
# @!attribute display_international
|
223
|
-
# @return [String] Well formatted international representation of this phone number for display purposes.
|
224
|
-
|
225
|
-
attr_accessor :country_code, :number, :extension, :type, :raw, :display, :display_international
|
226
|
-
|
227
|
-
def initialize(params={})
|
228
|
-
super params
|
229
|
-
@country_code = params[:country_code]
|
230
|
-
@number = params[:number]
|
231
|
-
@extension = params[:extension]
|
232
|
-
@type = params[:type]
|
233
|
-
@raw = params[:raw]
|
234
|
-
@display = params[:display]
|
235
|
-
@display_international = params[:display_international]
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.extra_metadata
|
239
|
-
[:display_international]
|
240
|
-
end
|
241
|
-
|
242
|
-
def to_hash
|
243
|
-
{country_code: @country_code, number: @number, extension: @extension, raw: @raw}
|
244
|
-
.reject { |_, value| value.nil? }
|
245
|
-
end
|
246
|
-
|
247
|
-
def is_searchable?
|
248
|
-
(@raw and not @raw.empty?) or not @number.nil?
|
249
|
-
end
|
250
|
-
|
251
|
-
end
|
252
|
-
|
253
|
-
|
254
|
-
class Email < Field
|
255
|
-
|
256
|
-
RE_EMAIL = Regexp.new('^[a-zA-Z0-9\'._%\-+]+@[a-zA-Z0-9._%\-]+\.[a-zA-Z]{2,24}$')
|
257
|
-
|
258
|
-
# @!attribute address
|
259
|
-
# @return [String] Plain email address
|
260
|
-
# @!attribute address_md5
|
261
|
-
# @return [String] MD5 hash of the email address
|
262
|
-
# @!attribute type
|
263
|
-
# @return [String] Type of email association to a person. One of `personal` or `work`.
|
264
|
-
# @!attribute disposable
|
265
|
-
# @return [Boolean] Indicating if this email comes from a disposable email provider.
|
266
|
-
# @!attribute email_provider
|
267
|
-
# @return [Boolean] Indicating if this email comes from a well known email provider like gmail or yahoo.
|
268
|
-
|
269
|
-
attr_accessor :address, :address_md5, :type, :disposable, :email_provider
|
270
|
-
|
271
|
-
def initialize(params={})
|
272
|
-
super params
|
273
|
-
@address = params[:address]
|
274
|
-
@address_md5 = params[:address_md5]
|
275
|
-
@type = params[:type]
|
276
|
-
@disposable = params[:disposable]
|
277
|
-
@email_provider = params[:email_provider]
|
278
|
-
end
|
279
|
-
|
280
|
-
def self.extra_metadata
|
281
|
-
[:disposable, :email_provider]
|
282
|
-
end
|
283
|
-
|
284
|
-
def is_valid_email?
|
285
|
-
not RE_EMAIL.match(@address).nil?
|
286
|
-
end
|
287
|
-
|
288
|
-
def is_searchable?
|
289
|
-
is_valid_email? or (not @address_md5.nil? and @address_md5.length == 32)
|
290
|
-
end
|
291
|
-
|
292
|
-
def to_hash
|
293
|
-
{address: @address, address_md5: @address_md5}.reject { |_, value| value.nil? }
|
294
|
-
end
|
295
|
-
|
296
|
-
def username
|
297
|
-
@address.split('@')[0] if is_valid_email?
|
298
|
-
end
|
299
|
-
|
300
|
-
def domain
|
301
|
-
@address.split('@')[1] if is_valid_email?
|
302
|
-
end
|
303
|
-
|
304
|
-
end
|
305
|
-
|
306
|
-
|
307
|
-
class Job < Field
|
308
|
-
|
309
|
-
attr_accessor :title, :organization, :industry, :date_range, :display
|
310
|
-
|
311
|
-
def initialize(params={})
|
312
|
-
super params
|
313
|
-
@title = params[:title]
|
314
|
-
@organization = params[:organization]
|
315
|
-
@industry = params[:industry]
|
316
|
-
@date_range = params[:date_range]
|
317
|
-
@display = params[:display]
|
318
|
-
end
|
319
|
-
|
320
|
-
def to_hash
|
321
|
-
{title: @title, organization: @organization, industry: @industry,
|
322
|
-
date_range: @date_range ? @date_range.to_hash : nil}
|
323
|
-
.reject { |_, value| value.nil? }
|
324
|
-
end
|
325
|
-
|
326
|
-
def to_s
|
327
|
-
return @display if @display
|
328
|
-
|
329
|
-
if @title and @organization
|
330
|
-
s = @title + ' at ' + @organization
|
331
|
-
else
|
332
|
-
s = @title || @organization
|
333
|
-
end
|
334
|
-
|
335
|
-
if s and @industry
|
336
|
-
if @date_range
|
337
|
-
range = @date_range.years_range
|
338
|
-
s += ' (%s, %d-%d)' % [@industry, range[0], range[1]]
|
339
|
-
else
|
340
|
-
s += ' (%s)' % [@industry]
|
341
|
-
end
|
342
|
-
else
|
343
|
-
s = ((s || '') + ' ' + (@industry || '')).strip
|
344
|
-
if s and @date_range
|
345
|
-
range = @date_range.years_range
|
346
|
-
s += ' (%d-%d)' % [range[0], range[1]]
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
s ? Pipl::Utils.to_utf8(s) : ''
|
351
|
-
end
|
352
|
-
|
353
|
-
end
|
354
|
-
|
355
|
-
|
356
|
-
class Education < Field
|
357
|
-
|
358
|
-
attr_accessor :degree, :school, :date_range, :display
|
359
|
-
|
360
|
-
def initialize(params={})
|
361
|
-
super params
|
362
|
-
@degree = params[:degree]
|
363
|
-
@school = params[:school]
|
364
|
-
@date_range = params[:date_range]
|
365
|
-
@display = params[:display]
|
366
|
-
end
|
367
|
-
|
368
|
-
def to_hash
|
369
|
-
{degree: @degree, school: @school, date_range: @date_range ? @date_range.to_hash : nil}
|
370
|
-
.reject { |_, value| value.nil? }
|
371
|
-
end
|
372
|
-
|
373
|
-
def to_s
|
374
|
-
return @display if @display
|
375
|
-
|
376
|
-
if @degree and @school
|
377
|
-
s = @degree + ' from ' + @school
|
378
|
-
else
|
379
|
-
s = @degree || @school
|
380
|
-
end
|
381
|
-
|
382
|
-
if s and @date_range
|
383
|
-
range = @date_range.years_range
|
384
|
-
s += ' (%d-%d)' % [range[0], range[1]]
|
385
|
-
end
|
386
|
-
|
387
|
-
s ? Pipl::Utils.to_utf8(s) : ''
|
388
|
-
end
|
389
|
-
|
390
|
-
end
|
391
|
-
|
392
|
-
|
393
|
-
class Image < Field
|
394
|
-
|
395
|
-
attr_accessor :url, :thumbnail_token
|
396
|
-
|
397
|
-
def initialize(params={})
|
398
|
-
super params
|
399
|
-
@url = params[:url]
|
400
|
-
@thumbnail_token = params[:thumbnail_token]
|
401
|
-
end
|
402
|
-
|
403
|
-
def thumbnail_url(params={})
|
404
|
-
return unless @thumbnail_token
|
405
|
-
|
406
|
-
opts = {width: 100, height: 100, favicon: true, zoom_face: true, use_https: false}.merge(params)
|
407
|
-
schema = opts.delete(:use_https) ? 'https': 'http'
|
408
|
-
tokens = @thumbnail_token.gsub(/&dsid=.*/,'')
|
409
|
-
tokens += ',' + opts.delete(:fallback).thumbnail_token.gsub(/&dsid=.*/,'') if opts[:fallback]
|
410
|
-
query_params = ["tokens=#{tokens}"] + opts.map { |k, v| "#{k}=#{v}" unless v.nil? }
|
411
|
-
"#{schema}://thumb.pipl.com/image?#{query_params.compact.join('&')}"
|
412
|
-
end
|
413
|
-
|
414
|
-
end
|
415
|
-
|
416
|
-
|
417
|
-
class Username < Field
|
418
|
-
|
419
|
-
attr_accessor :content
|
420
|
-
|
421
|
-
def initialize(params={})
|
422
|
-
super params
|
423
|
-
@content = params[:content]
|
424
|
-
end
|
425
|
-
|
426
|
-
def to_hash
|
427
|
-
{content: @content} if @content
|
428
|
-
end
|
429
|
-
|
430
|
-
def is_searchable?
|
431
|
-
!@content.nil? and Pipl::Utils.alnum_chars(@content).length >
|
432
|
-
end
|
433
|
-
|
434
|
-
end
|
435
|
-
|
436
|
-
|
437
|
-
class UserID < Username
|
438
|
-
|
439
|
-
def is_searchable?
|
440
|
-
not /.+@.+/.match(@content).nil?
|
441
|
-
end
|
442
|
-
|
443
|
-
end
|
444
|
-
|
445
|
-
|
446
|
-
class DOB < Field
|
447
|
-
|
448
|
-
attr_accessor :date_range, :display
|
449
|
-
|
450
|
-
def initialize(params={})
|
451
|
-
super params
|
452
|
-
@date_range = params[:date_range]
|
453
|
-
@display = params[:display]
|
454
|
-
end
|
455
|
-
|
456
|
-
def self.from_birth_year(birth_year)
|
457
|
-
raise ArgumentError.new('birth_year must be positive') unless birth_year > 0
|
458
|
-
self.new({date_range: DateRange.from_years_range(birth_year, birth_year)})
|
459
|
-
end
|
460
|
-
|
461
|
-
def self.from_birth_date(birth_date)
|
462
|
-
raise ArgumentError.new('birth_date can\'t be in the future') unless birth_date <= Date.today
|
463
|
-
self.new({date_range: DateRange.new(birth_date, birth_date)})
|
464
|
-
end
|
465
|
-
|
466
|
-
def self.from_age(age)
|
467
|
-
self.from_age_range(age, age)
|
468
|
-
end
|
469
|
-
|
470
|
-
def self.from_age_range(start_age, end_age)
|
471
|
-
raise ArgumentError.new('start_age and end_age can\'t be negative') if start_age < 0 or end_age < 0
|
472
|
-
|
473
|
-
if start_age > end_age
|
474
|
-
start_age, end_age = end_age, start_age
|
475
|
-
end
|
476
|
-
|
477
|
-
today = Date.today
|
478
|
-
start_date = today << end_age * 12
|
479
|
-
start_date = start_date - 1
|
480
|
-
end_date = today << start_age * 12
|
481
|
-
self.new({date_range: Pipl::DateRange.new(start_date, end_date)})
|
482
|
-
end
|
483
|
-
|
484
|
-
def to_s
|
485
|
-
@display or Pipl::Utils.to_utf8(age.to_s)
|
486
|
-
end
|
487
|
-
|
488
|
-
def age
|
489
|
-
unless @date_range.nil?
|
490
|
-
dob = @date_range.middle
|
491
|
-
today = Date.today
|
492
|
-
diff = today.year - dob.year
|
493
|
-
diff = diff - 1 if dob.month > today.month or (dob.month >= today.month and dob.day > today.day)
|
494
|
-
diff
|
495
|
-
end
|
496
|
-
end
|
497
|
-
|
498
|
-
def age_range
|
499
|
-
if @date_range
|
500
|
-
return [self.age, self.age] unless @date_range.start and @date_range.end
|
501
|
-
start_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.start, @date_range.start)}).age
|
502
|
-
end_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.end, @date_range.end)}).age
|
503
|
-
return end_age, start_age
|
504
|
-
else
|
505
|
-
return nil, nil
|
506
|
-
end
|
507
|
-
end
|
508
|
-
|
509
|
-
def to_hash
|
510
|
-
{date_range: @date_range.to_hash} if @date_range
|
511
|
-
end
|
512
|
-
|
513
|
-
def is_searchable?
|
514
|
-
not @date_range.nil?
|
515
|
-
end
|
516
|
-
|
517
|
-
end
|
518
|
-
|
519
|
-
|
520
|
-
class Url < Field
|
521
|
-
# @!attribute url
|
522
|
-
# @return [String] Actual Url
|
523
|
-
# @!attribute category
|
524
|
-
# @return [String] Category of the domain
|
525
|
-
# Possible values are:
|
526
|
-
# background_reports
|
527
|
-
# contact_details
|
528
|
-
# email_address
|
529
|
-
# media
|
530
|
-
# personal_profiles
|
531
|
-
# professional_and_business
|
532
|
-
# public_records
|
533
|
-
# publications
|
534
|
-
# school_and_classmates
|
535
|
-
# web_pages
|
536
|
-
# @!attribute domain
|
537
|
-
# @return [String] Canonical domain of the url
|
538
|
-
# @!attribute name
|
539
|
-
# @return [String] Name of the website hosting the url
|
540
|
-
# @!attribute sponsored
|
541
|
-
# @return [Boolean] Indicate if this url comes from a sponsored data source
|
542
|
-
# @!attribute sponsored
|
543
|
-
# @return [String] Unique identifier of this url
|
544
|
-
|
545
|
-
attr_accessor :url, :category, :domain, :name, :sponsored, :source_id
|
546
|
-
|
547
|
-
def initialize(params={})
|
548
|
-
super params
|
549
|
-
@url = params[:url]
|
550
|
-
@category = params[:category]
|
551
|
-
@domain = params[:domain]
|
552
|
-
@name = params[:name]
|
553
|
-
@sponsored = params[:sponsored]
|
554
|
-
@source_id = params[:source_id]
|
555
|
-
end
|
556
|
-
|
557
|
-
def self.extra_metadata
|
558
|
-
[:category, :domain, :name, :sponsored, :source_id]
|
559
|
-
end
|
560
|
-
|
561
|
-
def is_searchable?
|
562
|
-
not @url.to_s.empty?
|
563
|
-
end
|
564
|
-
|
565
|
-
end
|
566
|
-
|
567
|
-
|
568
|
-
class Gender < Field
|
569
|
-
|
570
|
-
attr_accessor :content
|
571
|
-
|
572
|
-
def initialize(params={})
|
573
|
-
super params
|
574
|
-
@content = params[:content]
|
575
|
-
end
|
576
|
-
|
577
|
-
def to_s
|
578
|
-
Pipl::Utils.titleize @content if @content
|
579
|
-
end
|
580
|
-
|
581
|
-
def to_hash
|
582
|
-
{content: @content} if @content
|
583
|
-
end
|
584
|
-
|
585
|
-
end
|
586
|
-
|
587
|
-
|
588
|
-
class Ethnicity < Field
|
589
|
-
|
590
|
-
# @!attribute content
|
591
|
-
# @return [String] Ethnicity name based on the U.S Census Bureau.
|
592
|
-
# Possible values are:
|
593
|
-
# white
|
594
|
-
# black
|
595
|
-
# american_indian
|
596
|
-
# alaska_native
|
597
|
-
# asian_indian
|
598
|
-
# chinese
|
599
|
-
# filipino
|
600
|
-
# other_asian
|
601
|
-
# japanese
|
602
|
-
# korean
|
603
|
-
# vietnamese
|
604
|
-
# native_hawaiian
|
605
|
-
# guamanian
|
606
|
-
# chamorro
|
607
|
-
# samoan
|
608
|
-
# other_pacific_islander
|
609
|
-
# other
|
610
|
-
|
611
|
-
attr_accessor :content
|
612
|
-
|
613
|
-
def initialize(params={})
|
614
|
-
super params
|
615
|
-
@content = params[:content]
|
616
|
-
end
|
617
|
-
|
618
|
-
def to_s
|
619
|
-
Pipl::Utils.titleize @content.gsub(/_/, ' ') if @content
|
620
|
-
end
|
621
|
-
|
622
|
-
end
|
623
|
-
|
624
|
-
|
625
|
-
class Language < Field
|
626
|
-
|
627
|
-
attr_accessor :language, :region, :display
|
628
|
-
|
629
|
-
def initialize(params={})
|
630
|
-
super params
|
631
|
-
@language = params[:language]
|
632
|
-
@region = params[:region]
|
633
|
-
@display = params[:display]
|
634
|
-
end
|
635
|
-
|
636
|
-
def to_s
|
637
|
-
return @display if @display
|
638
|
-
return "#{@language}_#{@region}" if @language and @region
|
639
|
-
return @language if @language and not @language.empty?
|
640
|
-
@region
|
641
|
-
end
|
642
|
-
|
643
|
-
end
|
644
|
-
|
645
|
-
|
646
|
-
class OriginCountry < Field
|
647
|
-
|
648
|
-
attr_accessor :country
|
649
|
-
|
650
|
-
def initialize(params={})
|
651
|
-
super params
|
652
|
-
@country = params[:country]
|
653
|
-
end
|
654
|
-
|
655
|
-
def to_s
|
656
|
-
Pipl::COUNTRIES[@country.upcase.to_sym] if @country
|
657
|
-
end
|
658
|
-
|
659
|
-
end
|
660
|
-
|
661
|
-
|
662
|
-
class Tag < Field
|
663
|
-
|
664
|
-
attr_accessor :content, :classification
|
665
|
-
|
666
|
-
def initialize(params={})
|
667
|
-
super params
|
668
|
-
@content = params[:content]
|
669
|
-
@classification = params[:classification]
|
670
|
-
end
|
671
|
-
|
672
|
-
def self.extra_metadata
|
673
|
-
[:classification]
|
674
|
-
end
|
675
|
-
|
676
|
-
def to_s
|
677
|
-
@content
|
678
|
-
end
|
679
|
-
|
680
|
-
end
|
681
|
-
|
682
|
-
|
683
|
-
class DateRange
|
684
|
-
|
685
|
-
attr_reader :start, :end
|
686
|
-
|
687
|
-
def initialize(start, end_)
|
688
|
-
@start = start
|
689
|
-
@end = end_
|
690
|
-
if @start and @end and @start > @end
|
691
|
-
@start, @end = @end, @start
|
692
|
-
end
|
693
|
-
end
|
694
|
-
|
695
|
-
# def ==(other)
|
696
|
-
# other.instance_of?(self.class) and inspect == other.inspect
|
697
|
-
# end
|
698
|
-
#
|
699
|
-
# alias_method :eql?, :==
|
700
|
-
|
701
|
-
def is_exact?
|
702
|
-
@start and @end and @start == @end
|
703
|
-
end
|
704
|
-
|
705
|
-
def middle
|
706
|
-
@start and @end ? @start + ((@end - @start) / 2) : @start or @end
|
707
|
-
end
|
708
|
-
|
709
|
-
def years_range
|
710
|
-
[@start.year, @end.year] if @start and @end
|
711
|
-
end
|
712
|
-
|
713
|
-
def self.from_years_range(start_year, end_year)
|
714
|
-
self.new(Date.new(start_year, 1, 1), Date.new(end_year, 12, 31))
|
715
|
-
end
|
716
|
-
|
717
|
-
def self.from_hash(h)
|
718
|
-
start_, end_ = h[:start], h[:end]
|
719
|
-
initializing_start = start_ ? Date.strptime(start_, Pipl::DATE_FORMAT) : nil
|
720
|
-
initializing_end = end_ ? Date.strptime(end_, Pipl::DATE_FORMAT) : nil
|
721
|
-
self.new(initializing_start, initializing_end)
|
722
|
-
end
|
723
|
-
|
724
|
-
def to_hash
|
725
|
-
h = {}
|
726
|
-
h[:start] = @start.strftime(Pipl::DATE_FORMAT) if @start
|
727
|
-
h[:end] = @end.strftime(Pipl::DATE_FORMAT) if @end
|
728
|
-
h
|
729
|
-
end
|
730
|
-
end
|
731
|
-
|
732
|
-
end
|
1
|
+
require 'date'
|
2
|
+
require 'set'
|
3
|
+
require_relative 'consts'
|
4
|
+
require_relative 'utils'
|
5
|
+
|
6
|
+
module Pipl
|
7
|
+
|
8
|
+
class Field
|
9
|
+
|
10
|
+
include Pipl::Utils
|
11
|
+
|
12
|
+
attr_accessor :valid_since, :last_seen, :inferred, :current
|
13
|
+
|
14
|
+
def initialize(params={})
|
15
|
+
@valid_since = params[:valid_since]
|
16
|
+
@last_seen = params[:last_seen]
|
17
|
+
@inferred = params[:inferred]
|
18
|
+
@current = params[:current]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_hash(h)
|
22
|
+
params = base_params_from_hash h
|
23
|
+
extra_metadata.each { |p| params[p] = h["@#{p}".to_sym] }
|
24
|
+
params = h.merge params
|
25
|
+
self.new(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.base_params_from_hash(h)
|
29
|
+
params = {
|
30
|
+
inferred: h[:@inferred],
|
31
|
+
current: h[:@current],
|
32
|
+
type: h[:@type],
|
33
|
+
display: h[:display]
|
34
|
+
}
|
35
|
+
params[:valid_since] = Date.strptime(h[:@valid_since], Pipl::DATE_FORMAT) if h.key? :@valid_since
|
36
|
+
params[:last_seen] = Date.strptime(h[:@last_seen], Pipl::DATE_FORMAT) if h.key? :@last_seen
|
37
|
+
params[:date_range] = Pipl::DateRange.from_hash(h[:date_range]) if h.key? :date_range
|
38
|
+
params
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.extra_metadata
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_searchable?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class Name < Field
|
57
|
+
# @!attribute first
|
58
|
+
# @return [String] First name
|
59
|
+
# @!attribute middle
|
60
|
+
# @return [String] Middle name or initial
|
61
|
+
# @!attribute last
|
62
|
+
# @return [String] Last name
|
63
|
+
# @!attribute prefix
|
64
|
+
# @return [String] Name prefix
|
65
|
+
# @!attribute suffix
|
66
|
+
# @return [String] Name suffix
|
67
|
+
# @!attribute type
|
68
|
+
# @return [String] Type of association of this name to a person. One of `present`, `maiden`, `former` or `alias`.
|
69
|
+
|
70
|
+
attr_accessor :first, :middle, :last, :prefix, :suffix, :type, :raw, :display
|
71
|
+
|
72
|
+
def initialize(params={})
|
73
|
+
super params
|
74
|
+
@first = params[:first]
|
75
|
+
@middle = params[:middle]
|
76
|
+
@last = params[:last]
|
77
|
+
@prefix = params[:prefix]
|
78
|
+
@suffix = params[:suffix]
|
79
|
+
@type = params[:type]
|
80
|
+
@raw = params[:raw]
|
81
|
+
@display = params[:display]
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_hash
|
85
|
+
{first: @first, middle: @middle, last: @last, prefix: @prefix, suffix: @suffix, raw: @raw}
|
86
|
+
.reject { |_, value| value.nil? }
|
87
|
+
end
|
88
|
+
|
89
|
+
def is_searchable?
|
90
|
+
first = Pipl::Utils.alpha_chars(@first || '')
|
91
|
+
last = Pipl::Utils.alpha_chars(@last || '')
|
92
|
+
raw = Pipl::Utils.alpha_chars(@raw || '')
|
93
|
+
(first.length > 1 and last.length > 1) or raw.length > 3
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
return @display if @display
|
98
|
+
|
99
|
+
vals = [@prefix, @first, @middle, @last, @suffix]
|
100
|
+
s = vals.any? ? vals.select { |v| v }.map { |v| v.capitalize }.join(' ') : nil
|
101
|
+
s ? Pipl::Utils.to_utf8(s) : ''
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
class Address < Field
|
108
|
+
# @!attribute country
|
109
|
+
# @return [String] 2 letters country code
|
110
|
+
# @!attribute state
|
111
|
+
# @return [String] 2 letters state code
|
112
|
+
# @!attribute city
|
113
|
+
# @return [String] City
|
114
|
+
# @!attribute street
|
115
|
+
# @return [String] Street
|
116
|
+
# @!attribute house
|
117
|
+
# @return [String] House
|
118
|
+
# @!attribute apartment
|
119
|
+
# @return [String] Apartment
|
120
|
+
# @!attribute zip_code
|
121
|
+
# @return [String] Zip Code
|
122
|
+
# @!attribute po_box
|
123
|
+
# @return [String] Post Office box number
|
124
|
+
# @!attribute type
|
125
|
+
# @return [String] Type of association of this address to a person. One of `home`, `work` or `old`.
|
126
|
+
# @!attribute raw
|
127
|
+
# @return [String] Unparsed address.
|
128
|
+
# @!attribute display
|
129
|
+
# @return [String] well formatted representation of this address for display purposes.
|
130
|
+
|
131
|
+
attr_accessor :country, :state, :city, :street, :house, :apartment, :zip_code, :po_box, :type, :raw, :display
|
132
|
+
|
133
|
+
def initialize(params={})
|
134
|
+
super params
|
135
|
+
@country = params[:country]
|
136
|
+
@state = params[:state]
|
137
|
+
@city = params[:city]
|
138
|
+
@street = params[:street]
|
139
|
+
@house = params[:house]
|
140
|
+
@apartment = params[:apartment]
|
141
|
+
@zip_code = params[:zip_code]
|
142
|
+
@po_box = params[:po_box]
|
143
|
+
@type = params[:type]
|
144
|
+
@raw = params[:raw]
|
145
|
+
@display = params[:display]
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_valid_country?
|
149
|
+
@country and Pipl::COUNTRIES.key? @country.upcase.to_sym
|
150
|
+
end
|
151
|
+
|
152
|
+
def is_valid_state?
|
153
|
+
is_valid_country? and Pipl::STATES.key?(@country.upcase.to_sym) and
|
154
|
+
@state and Pipl::STATES[@country.upcase.to_sym].key?(@state.upcase.to_sym)
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_hash
|
158
|
+
{country: @country, state: @state, city: @city, street: @street, house: @house, apartment: @apartment,
|
159
|
+
zip_code: @zip_code, po_box: @po_box, raw: @raw}
|
160
|
+
.reject { |_, value| value.nil? }
|
161
|
+
end
|
162
|
+
|
163
|
+
def is_searchable?
|
164
|
+
[@raw, @country, @state, @city].any? {|x| not x.to_s.empty?}
|
165
|
+
end
|
166
|
+
|
167
|
+
def is_sole_searchable?
|
168
|
+
not @raw.to_s.empty? or [@city, @street, @house].all? {|x| not x.to_s.empty?}
|
169
|
+
end
|
170
|
+
|
171
|
+
def country_full
|
172
|
+
Pipl::COUNTRIES[@country.upcase.to_sym] if @country
|
173
|
+
end
|
174
|
+
|
175
|
+
def state_full
|
176
|
+
Pipl::STATES[@country.upcase.to_sym][@state.upcase.to_sym] if is_valid_state?
|
177
|
+
end
|
178
|
+
|
179
|
+
def to_s
|
180
|
+
return @display if @display
|
181
|
+
|
182
|
+
country = @state ? @country : country_full
|
183
|
+
state = @city ? @state : state_full
|
184
|
+
vals = [@street, @city, state, country]
|
185
|
+
s = vals.any? ? vals.select { |v| v }.join(', ') : ''
|
186
|
+
|
187
|
+
if @street and (@house or @apartment)
|
188
|
+
prefix = [@house, @apartment].select { |v| v and not v.empty? }.join('-')
|
189
|
+
s = prefix + ' ' + (s || '')
|
190
|
+
end
|
191
|
+
|
192
|
+
if @po_box and @street.nil?
|
193
|
+
s = "P.O. Box #{@po_box} " + (s || '')
|
194
|
+
end
|
195
|
+
|
196
|
+
s ? Pipl::Utils.to_utf8(s) : ''
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
class Phone < Field
|
203
|
+
# @!attribute country_code
|
204
|
+
# @return [Fixnum] International country calling code
|
205
|
+
# @!attribute number
|
206
|
+
# @return [Fixnum] Actual phone number
|
207
|
+
# @!attribute extension
|
208
|
+
# @return [String] Office extension
|
209
|
+
# @!attribute type
|
210
|
+
# @return [String] Type of association of this phone to a person.
|
211
|
+
# Possible values are:
|
212
|
+
# mobile
|
213
|
+
# home_phone
|
214
|
+
# home_fax
|
215
|
+
# work_phone
|
216
|
+
# work_fax
|
217
|
+
# pager
|
218
|
+
# @!attribute raw
|
219
|
+
# @return [String] Unparsed phone number
|
220
|
+
# @!attribute display
|
221
|
+
# @return [String] Well formatted representation of this phone number for display purposes.
|
222
|
+
# @!attribute display_international
|
223
|
+
# @return [String] Well formatted international representation of this phone number for display purposes.
|
224
|
+
|
225
|
+
attr_accessor :country_code, :number, :extension, :type, :raw, :display, :display_international
|
226
|
+
|
227
|
+
def initialize(params={})
|
228
|
+
super params
|
229
|
+
@country_code = params[:country_code]
|
230
|
+
@number = params[:number]
|
231
|
+
@extension = params[:extension]
|
232
|
+
@type = params[:type]
|
233
|
+
@raw = params[:raw]
|
234
|
+
@display = params[:display]
|
235
|
+
@display_international = params[:display_international]
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.extra_metadata
|
239
|
+
[:display_international]
|
240
|
+
end
|
241
|
+
|
242
|
+
def to_hash
|
243
|
+
{country_code: @country_code, number: @number, extension: @extension, raw: @raw}
|
244
|
+
.reject { |_, value| value.nil? }
|
245
|
+
end
|
246
|
+
|
247
|
+
def is_searchable?
|
248
|
+
(@raw and not @raw.empty?) or not @number.nil?
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
class Email < Field
|
255
|
+
|
256
|
+
RE_EMAIL = Regexp.new('^[a-zA-Z0-9\'._%\-+]+@[a-zA-Z0-9._%\-]+\.[a-zA-Z]{2,24}$')
|
257
|
+
|
258
|
+
# @!attribute address
|
259
|
+
# @return [String] Plain email address
|
260
|
+
# @!attribute address_md5
|
261
|
+
# @return [String] MD5 hash of the email address
|
262
|
+
# @!attribute type
|
263
|
+
# @return [String] Type of email association to a person. One of `personal` or `work`.
|
264
|
+
# @!attribute disposable
|
265
|
+
# @return [Boolean] Indicating if this email comes from a disposable email provider.
|
266
|
+
# @!attribute email_provider
|
267
|
+
# @return [Boolean] Indicating if this email comes from a well known email provider like gmail or yahoo.
|
268
|
+
|
269
|
+
attr_accessor :address, :address_md5, :type, :disposable, :email_provider
|
270
|
+
|
271
|
+
def initialize(params={})
|
272
|
+
super params
|
273
|
+
@address = params[:address]
|
274
|
+
@address_md5 = params[:address_md5]
|
275
|
+
@type = params[:type]
|
276
|
+
@disposable = params[:disposable]
|
277
|
+
@email_provider = params[:email_provider]
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.extra_metadata
|
281
|
+
[:disposable, :email_provider]
|
282
|
+
end
|
283
|
+
|
284
|
+
def is_valid_email?
|
285
|
+
not RE_EMAIL.match(@address).nil?
|
286
|
+
end
|
287
|
+
|
288
|
+
def is_searchable?
|
289
|
+
is_valid_email? or (not @address_md5.nil? and @address_md5.length == 32)
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_hash
|
293
|
+
{address: @address, address_md5: @address_md5}.reject { |_, value| value.nil? }
|
294
|
+
end
|
295
|
+
|
296
|
+
def username
|
297
|
+
@address.split('@')[0] if is_valid_email?
|
298
|
+
end
|
299
|
+
|
300
|
+
def domain
|
301
|
+
@address.split('@')[1] if is_valid_email?
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
class Job < Field
|
308
|
+
|
309
|
+
attr_accessor :title, :organization, :industry, :date_range, :display
|
310
|
+
|
311
|
+
def initialize(params={})
|
312
|
+
super params
|
313
|
+
@title = params[:title]
|
314
|
+
@organization = params[:organization]
|
315
|
+
@industry = params[:industry]
|
316
|
+
@date_range = params[:date_range]
|
317
|
+
@display = params[:display]
|
318
|
+
end
|
319
|
+
|
320
|
+
def to_hash
|
321
|
+
{title: @title, organization: @organization, industry: @industry,
|
322
|
+
date_range: @date_range ? @date_range.to_hash : nil}
|
323
|
+
.reject { |_, value| value.nil? }
|
324
|
+
end
|
325
|
+
|
326
|
+
def to_s
|
327
|
+
return @display if @display
|
328
|
+
|
329
|
+
if @title and @organization
|
330
|
+
s = @title + ' at ' + @organization
|
331
|
+
else
|
332
|
+
s = @title || @organization
|
333
|
+
end
|
334
|
+
|
335
|
+
if s and @industry
|
336
|
+
if @date_range
|
337
|
+
range = @date_range.years_range
|
338
|
+
s += ' (%s, %d-%d)' % [@industry, range[0], range[1]]
|
339
|
+
else
|
340
|
+
s += ' (%s)' % [@industry]
|
341
|
+
end
|
342
|
+
else
|
343
|
+
s = ((s || '') + ' ' + (@industry || '')).strip
|
344
|
+
if s and @date_range
|
345
|
+
range = @date_range.years_range
|
346
|
+
s += ' (%d-%d)' % [range[0], range[1]]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
s ? Pipl::Utils.to_utf8(s) : ''
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
class Education < Field
|
357
|
+
|
358
|
+
attr_accessor :degree, :school, :date_range, :display
|
359
|
+
|
360
|
+
def initialize(params={})
|
361
|
+
super params
|
362
|
+
@degree = params[:degree]
|
363
|
+
@school = params[:school]
|
364
|
+
@date_range = params[:date_range]
|
365
|
+
@display = params[:display]
|
366
|
+
end
|
367
|
+
|
368
|
+
def to_hash
|
369
|
+
{degree: @degree, school: @school, date_range: @date_range ? @date_range.to_hash : nil}
|
370
|
+
.reject { |_, value| value.nil? }
|
371
|
+
end
|
372
|
+
|
373
|
+
def to_s
|
374
|
+
return @display if @display
|
375
|
+
|
376
|
+
if @degree and @school
|
377
|
+
s = @degree + ' from ' + @school
|
378
|
+
else
|
379
|
+
s = @degree || @school
|
380
|
+
end
|
381
|
+
|
382
|
+
if s and @date_range
|
383
|
+
range = @date_range.years_range
|
384
|
+
s += ' (%d-%d)' % [range[0], range[1]]
|
385
|
+
end
|
386
|
+
|
387
|
+
s ? Pipl::Utils.to_utf8(s) : ''
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
class Image < Field
|
394
|
+
|
395
|
+
attr_accessor :url, :thumbnail_token
|
396
|
+
|
397
|
+
def initialize(params={})
|
398
|
+
super params
|
399
|
+
@url = params[:url]
|
400
|
+
@thumbnail_token = params[:thumbnail_token]
|
401
|
+
end
|
402
|
+
|
403
|
+
def thumbnail_url(params={})
|
404
|
+
return unless @thumbnail_token
|
405
|
+
|
406
|
+
opts = {width: 100, height: 100, favicon: true, zoom_face: true, use_https: false}.merge(params)
|
407
|
+
schema = opts.delete(:use_https) ? 'https': 'http'
|
408
|
+
tokens = @thumbnail_token.gsub(/&dsid=.*/,'')
|
409
|
+
tokens += ',' + opts.delete(:fallback).thumbnail_token.gsub(/&dsid=.*/,'') if opts[:fallback]
|
410
|
+
query_params = ["tokens=#{tokens}"] + opts.map { |k, v| "#{k}=#{v}" unless v.nil? }
|
411
|
+
"#{schema}://thumb.pipl.com/image?#{query_params.compact.join('&')}"
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
|
417
|
+
class Username < Field
|
418
|
+
|
419
|
+
attr_accessor :content
|
420
|
+
|
421
|
+
def initialize(params={})
|
422
|
+
super params
|
423
|
+
@content = params[:content]
|
424
|
+
end
|
425
|
+
|
426
|
+
def to_hash
|
427
|
+
{content: @content} if @content
|
428
|
+
end
|
429
|
+
|
430
|
+
def is_searchable?
|
431
|
+
!@content.nil? and Pipl::Utils.alnum_chars(@content).length > 2
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
class UserID < Username
|
438
|
+
|
439
|
+
def is_searchable?
|
440
|
+
not /.+@.+/.match(@content).nil?
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
|
446
|
+
class DOB < Field
|
447
|
+
|
448
|
+
attr_accessor :date_range, :display
|
449
|
+
|
450
|
+
def initialize(params={})
|
451
|
+
super params
|
452
|
+
@date_range = params[:date_range]
|
453
|
+
@display = params[:display]
|
454
|
+
end
|
455
|
+
|
456
|
+
def self.from_birth_year(birth_year)
|
457
|
+
raise ArgumentError.new('birth_year must be positive') unless birth_year > 0
|
458
|
+
self.new({date_range: DateRange.from_years_range(birth_year, birth_year)})
|
459
|
+
end
|
460
|
+
|
461
|
+
def self.from_birth_date(birth_date)
|
462
|
+
raise ArgumentError.new('birth_date can\'t be in the future') unless birth_date <= Date.today
|
463
|
+
self.new({date_range: DateRange.new(birth_date, birth_date)})
|
464
|
+
end
|
465
|
+
|
466
|
+
def self.from_age(age)
|
467
|
+
self.from_age_range(age, age)
|
468
|
+
end
|
469
|
+
|
470
|
+
def self.from_age_range(start_age, end_age)
|
471
|
+
raise ArgumentError.new('start_age and end_age can\'t be negative') if start_age < 0 or end_age < 0
|
472
|
+
|
473
|
+
if start_age > end_age
|
474
|
+
start_age, end_age = end_age, start_age
|
475
|
+
end
|
476
|
+
|
477
|
+
today = Date.today
|
478
|
+
start_date = today << end_age * 12
|
479
|
+
start_date = start_date - 1
|
480
|
+
end_date = today << start_age * 12
|
481
|
+
self.new({date_range: Pipl::DateRange.new(start_date, end_date)})
|
482
|
+
end
|
483
|
+
|
484
|
+
def to_s
|
485
|
+
@display or Pipl::Utils.to_utf8(age.to_s)
|
486
|
+
end
|
487
|
+
|
488
|
+
def age
|
489
|
+
unless @date_range.nil?
|
490
|
+
dob = @date_range.middle
|
491
|
+
today = Date.today
|
492
|
+
diff = today.year - dob.year
|
493
|
+
diff = diff - 1 if dob.month > today.month or (dob.month >= today.month and dob.day > today.day)
|
494
|
+
diff
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def age_range
|
499
|
+
if @date_range
|
500
|
+
return [self.age, self.age] unless @date_range.start and @date_range.end
|
501
|
+
start_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.start, @date_range.start)}).age
|
502
|
+
end_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.end, @date_range.end)}).age
|
503
|
+
return end_age, start_age
|
504
|
+
else
|
505
|
+
return nil, nil
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def to_hash
|
510
|
+
{date_range: @date_range.to_hash} if @date_range
|
511
|
+
end
|
512
|
+
|
513
|
+
def is_searchable?
|
514
|
+
not @date_range.nil?
|
515
|
+
end
|
516
|
+
|
517
|
+
end
|
518
|
+
|
519
|
+
|
520
|
+
class Url < Field
|
521
|
+
# @!attribute url
|
522
|
+
# @return [String] Actual Url
|
523
|
+
# @!attribute category
|
524
|
+
# @return [String] Category of the domain
|
525
|
+
# Possible values are:
|
526
|
+
# background_reports
|
527
|
+
# contact_details
|
528
|
+
# email_address
|
529
|
+
# media
|
530
|
+
# personal_profiles
|
531
|
+
# professional_and_business
|
532
|
+
# public_records
|
533
|
+
# publications
|
534
|
+
# school_and_classmates
|
535
|
+
# web_pages
|
536
|
+
# @!attribute domain
|
537
|
+
# @return [String] Canonical domain of the url
|
538
|
+
# @!attribute name
|
539
|
+
# @return [String] Name of the website hosting the url
|
540
|
+
# @!attribute sponsored
|
541
|
+
# @return [Boolean] Indicate if this url comes from a sponsored data source
|
542
|
+
# @!attribute sponsored
|
543
|
+
# @return [String] Unique identifier of this url
|
544
|
+
|
545
|
+
attr_accessor :url, :category, :domain, :name, :sponsored, :source_id
|
546
|
+
|
547
|
+
def initialize(params={})
|
548
|
+
super params
|
549
|
+
@url = params[:url]
|
550
|
+
@category = params[:category]
|
551
|
+
@domain = params[:domain]
|
552
|
+
@name = params[:name]
|
553
|
+
@sponsored = params[:sponsored]
|
554
|
+
@source_id = params[:source_id]
|
555
|
+
end
|
556
|
+
|
557
|
+
def self.extra_metadata
|
558
|
+
[:category, :domain, :name, :sponsored, :source_id]
|
559
|
+
end
|
560
|
+
|
561
|
+
def is_searchable?
|
562
|
+
not @url.to_s.empty?
|
563
|
+
end
|
564
|
+
|
565
|
+
end
|
566
|
+
|
567
|
+
|
568
|
+
class Gender < Field
|
569
|
+
|
570
|
+
attr_accessor :content
|
571
|
+
|
572
|
+
def initialize(params={})
|
573
|
+
super params
|
574
|
+
@content = params[:content]
|
575
|
+
end
|
576
|
+
|
577
|
+
def to_s
|
578
|
+
Pipl::Utils.titleize @content if @content
|
579
|
+
end
|
580
|
+
|
581
|
+
def to_hash
|
582
|
+
{content: @content} if @content
|
583
|
+
end
|
584
|
+
|
585
|
+
end
|
586
|
+
|
587
|
+
|
588
|
+
class Ethnicity < Field
|
589
|
+
|
590
|
+
# @!attribute content
|
591
|
+
# @return [String] Ethnicity name based on the U.S Census Bureau.
|
592
|
+
# Possible values are:
|
593
|
+
# white
|
594
|
+
# black
|
595
|
+
# american_indian
|
596
|
+
# alaska_native
|
597
|
+
# asian_indian
|
598
|
+
# chinese
|
599
|
+
# filipino
|
600
|
+
# other_asian
|
601
|
+
# japanese
|
602
|
+
# korean
|
603
|
+
# vietnamese
|
604
|
+
# native_hawaiian
|
605
|
+
# guamanian
|
606
|
+
# chamorro
|
607
|
+
# samoan
|
608
|
+
# other_pacific_islander
|
609
|
+
# other
|
610
|
+
|
611
|
+
attr_accessor :content
|
612
|
+
|
613
|
+
def initialize(params={})
|
614
|
+
super params
|
615
|
+
@content = params[:content]
|
616
|
+
end
|
617
|
+
|
618
|
+
def to_s
|
619
|
+
Pipl::Utils.titleize @content.gsub(/_/, ' ') if @content
|
620
|
+
end
|
621
|
+
|
622
|
+
end
|
623
|
+
|
624
|
+
|
625
|
+
class Language < Field
|
626
|
+
|
627
|
+
attr_accessor :language, :region, :display
|
628
|
+
|
629
|
+
def initialize(params={})
|
630
|
+
super params
|
631
|
+
@language = params[:language]
|
632
|
+
@region = params[:region]
|
633
|
+
@display = params[:display]
|
634
|
+
end
|
635
|
+
|
636
|
+
def to_s
|
637
|
+
return @display if @display
|
638
|
+
return "#{@language}_#{@region}" if @language and @region
|
639
|
+
return @language if @language and not @language.empty?
|
640
|
+
@region
|
641
|
+
end
|
642
|
+
|
643
|
+
end
|
644
|
+
|
645
|
+
|
646
|
+
class OriginCountry < Field
|
647
|
+
|
648
|
+
attr_accessor :country
|
649
|
+
|
650
|
+
def initialize(params={})
|
651
|
+
super params
|
652
|
+
@country = params[:country]
|
653
|
+
end
|
654
|
+
|
655
|
+
def to_s
|
656
|
+
Pipl::COUNTRIES[@country.upcase.to_sym] if @country
|
657
|
+
end
|
658
|
+
|
659
|
+
end
|
660
|
+
|
661
|
+
|
662
|
+
class Tag < Field
|
663
|
+
|
664
|
+
attr_accessor :content, :classification
|
665
|
+
|
666
|
+
def initialize(params={})
|
667
|
+
super params
|
668
|
+
@content = params[:content]
|
669
|
+
@classification = params[:classification]
|
670
|
+
end
|
671
|
+
|
672
|
+
def self.extra_metadata
|
673
|
+
[:classification]
|
674
|
+
end
|
675
|
+
|
676
|
+
def to_s
|
677
|
+
@content
|
678
|
+
end
|
679
|
+
|
680
|
+
end
|
681
|
+
|
682
|
+
|
683
|
+
class DateRange
|
684
|
+
|
685
|
+
attr_reader :start, :end
|
686
|
+
|
687
|
+
def initialize(start, end_)
|
688
|
+
@start = start
|
689
|
+
@end = end_
|
690
|
+
if @start and @end and @start > @end
|
691
|
+
@start, @end = @end, @start
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
# def ==(other)
|
696
|
+
# other.instance_of?(self.class) and inspect == other.inspect
|
697
|
+
# end
|
698
|
+
#
|
699
|
+
# alias_method :eql?, :==
|
700
|
+
|
701
|
+
def is_exact?
|
702
|
+
@start and @end and @start == @end
|
703
|
+
end
|
704
|
+
|
705
|
+
def middle
|
706
|
+
@start and @end ? @start + ((@end - @start) / 2) : @start or @end
|
707
|
+
end
|
708
|
+
|
709
|
+
def years_range
|
710
|
+
[@start.year, @end.year] if @start and @end
|
711
|
+
end
|
712
|
+
|
713
|
+
def self.from_years_range(start_year, end_year)
|
714
|
+
self.new(Date.new(start_year, 1, 1), Date.new(end_year, 12, 31))
|
715
|
+
end
|
716
|
+
|
717
|
+
def self.from_hash(h)
|
718
|
+
start_, end_ = h[:start], h[:end]
|
719
|
+
initializing_start = start_ ? Date.strptime(start_, Pipl::DATE_FORMAT) : nil
|
720
|
+
initializing_end = end_ ? Date.strptime(end_, Pipl::DATE_FORMAT) : nil
|
721
|
+
self.new(initializing_start, initializing_end)
|
722
|
+
end
|
723
|
+
|
724
|
+
def to_hash
|
725
|
+
h = {}
|
726
|
+
h[:start] = @start.strftime(Pipl::DATE_FORMAT) if @start
|
727
|
+
h[:end] = @end.strftime(Pipl::DATE_FORMAT) if @end
|
728
|
+
h
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
end
|