job_dollars 1.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 +7 -0
- data/lib/job_dollars.rb +30 -0
- data/lib/job_dollars.yml +329 -0
- data/lib/job_dollars/core_ext/array.rb +22 -0
- data/lib/job_dollars/core_ext/hash.rb +9 -0
- data/lib/job_dollars/core_ext/hash_with_indifferent_access.rb +174 -0
- data/lib/job_dollars/estimator.rb +184 -0
- metadata +56 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 93c5f216180ff72d2df380c54fe76c74c39514cb
|
|
4
|
+
data.tar.gz: 2f2ca2f47c34ba02c58eba37959285ef8b8e92c0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9945ef0043844f89ec810a371e4e746723259656746487d37bf78ce9dcd0d0aacf67630f6c5d30b5bd3e9f653c2297cb897ff6d5fb67085aacb41da4143bcf8e
|
|
7
|
+
data.tar.gz: 40662a9fa558a8aae8907f9986e55cc46c37d6a522e32985a751d70480792ff56aef7283f17265cc0dfb605b7193d042a8848b173b5e77ec7ed7ae8d0437654c
|
data/lib/job_dollars.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), './job_dollars/core_ext/hash.rb')
|
|
2
|
+
require File.join(File.dirname(__FILE__), './job_dollars/core_ext/hash_with_indifferent_access.rb')
|
|
3
|
+
require File.join(File.dirname(__FILE__), './job_dollars/estimator.rb')
|
|
4
|
+
|
|
5
|
+
require 'yaml'
|
|
6
|
+
|
|
7
|
+
# JobDollars is the main module to include to start using JobDollars. It automatically
|
|
8
|
+
# includes the built-in Estimator module, along with some constants.
|
|
9
|
+
#
|
|
10
|
+
# Example (IRB):
|
|
11
|
+
#
|
|
12
|
+
# irb(main):001:0> require 'job_dollars'
|
|
13
|
+
# => true
|
|
14
|
+
# irb(main):002:0> include JobDollars
|
|
15
|
+
# => Object
|
|
16
|
+
# irb(main):006:0> beginner_javagrad_rating(JOB_DATA['java-android'])
|
|
17
|
+
# => 17.541885876163494
|
|
18
|
+
module JobDollars
|
|
19
|
+
DATA_YML_FILE = File.join(File.dirname(__FILE__), './job_dollars.yml')
|
|
20
|
+
|
|
21
|
+
extend Estimator
|
|
22
|
+
|
|
23
|
+
def self.included(base)
|
|
24
|
+
base.include Estimator
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
JOB_DATA = YAML.load(File.read(DATA_YML_FILE))
|
|
28
|
+
JAVAGRAD_IN_JOB_DOLLARS = market_work_in_job_dollars(0.0, JOB_DATA["java-swing"])
|
|
29
|
+
end
|
|
30
|
+
|
data/lib/job_dollars.yml
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
--- !map:HashWithIndifferentAccess
|
|
2
|
+
|
|
3
|
+
csharp-dotnet:
|
|
4
|
+
salaries:
|
|
5
|
+
entry: >
|
|
6
|
+
$65,000+ (2686)
|
|
7
|
+
$75,000+ (2243)
|
|
8
|
+
$80,000+ (1913)
|
|
9
|
+
$90,000+ (1218)
|
|
10
|
+
$100,000+ (637)
|
|
11
|
+
|
|
12
|
+
mid: >
|
|
13
|
+
$85,000+ (1823)
|
|
14
|
+
$95,000+ (1544)
|
|
15
|
+
$105,000+ (1122)
|
|
16
|
+
$110,000+ (906)
|
|
17
|
+
$120,000+ (477)
|
|
18
|
+
|
|
19
|
+
senior: >
|
|
20
|
+
$100,000+ (610)
|
|
21
|
+
$110,000+ (482)
|
|
22
|
+
$115,000+ (363)
|
|
23
|
+
$120,000+ (280)
|
|
24
|
+
$130,000+ (142)
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
entry: 3137
|
|
28
|
+
mid: 11974
|
|
29
|
+
senior: 3422
|
|
30
|
+
|
|
31
|
+
resumes: 128_839
|
|
32
|
+
|
|
33
|
+
ruby-rails:
|
|
34
|
+
salaries:
|
|
35
|
+
entry: >
|
|
36
|
+
$75,000+ (777)
|
|
37
|
+
$90,000+ (634)
|
|
38
|
+
$100,000+ (500)
|
|
39
|
+
$105,000+ (383)
|
|
40
|
+
$115,000+ (198)
|
|
41
|
+
|
|
42
|
+
mid: >
|
|
43
|
+
$85,000+ (1823)
|
|
44
|
+
$95,000+ (1544)
|
|
45
|
+
$105,000+ (1122)
|
|
46
|
+
$110,000+ (906)
|
|
47
|
+
$120,000+ (477)
|
|
48
|
+
|
|
49
|
+
senior: >
|
|
50
|
+
$100,000+ (610)
|
|
51
|
+
$110,000+ (482)
|
|
52
|
+
$115,000+ (363)
|
|
53
|
+
$120,000+ (280)
|
|
54
|
+
$130,000+ (142)
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
entry: 908
|
|
58
|
+
mid: 2088
|
|
59
|
+
senior: 718
|
|
60
|
+
|
|
61
|
+
resumes: 20_269
|
|
62
|
+
|
|
63
|
+
javascript-redux:
|
|
64
|
+
salaries:
|
|
65
|
+
entry: >
|
|
66
|
+
$80,000+ (64)
|
|
67
|
+
$95,000+ (48)
|
|
68
|
+
$100,000+ (38)
|
|
69
|
+
$105,000+ (27)
|
|
70
|
+
$110,000+ (21)
|
|
71
|
+
|
|
72
|
+
mid: >
|
|
73
|
+
$90,000+ (127)
|
|
74
|
+
$100,000+ (106)
|
|
75
|
+
$105,000+ (83)
|
|
76
|
+
$110,000+ (64)
|
|
77
|
+
$120,000+ (29)
|
|
78
|
+
|
|
79
|
+
senior: >
|
|
80
|
+
$100,000+ (43)
|
|
81
|
+
$105,000+ (38)
|
|
82
|
+
$110,000+ (28)
|
|
83
|
+
$115,000+ (22)
|
|
84
|
+
$130,000+ (8)
|
|
85
|
+
|
|
86
|
+
jobs:
|
|
87
|
+
entry: 71
|
|
88
|
+
mid: 144
|
|
89
|
+
senior: 47
|
|
90
|
+
|
|
91
|
+
resumes: 448
|
|
92
|
+
|
|
93
|
+
java-swing:
|
|
94
|
+
salaries:
|
|
95
|
+
entry: >
|
|
96
|
+
$70,000+ (61)
|
|
97
|
+
$75,000+ (56)
|
|
98
|
+
$80,000+ (43)
|
|
99
|
+
$90,000+ (29)
|
|
100
|
+
$105,000+ (12)
|
|
101
|
+
|
|
102
|
+
mid: >
|
|
103
|
+
$85,000+ (249)
|
|
104
|
+
$90,000+ (223)
|
|
105
|
+
$100,000+ (148)
|
|
106
|
+
$105,000+ (117)
|
|
107
|
+
$115,000+ (50)
|
|
108
|
+
|
|
109
|
+
senior: >
|
|
110
|
+
$85,000+ (126)
|
|
111
|
+
$95,000+ (99)
|
|
112
|
+
$105,000+ (73)
|
|
113
|
+
$110,000+ (57)
|
|
114
|
+
$120,000+ (27)
|
|
115
|
+
|
|
116
|
+
jobs:
|
|
117
|
+
entry: 71
|
|
118
|
+
mid: 292
|
|
119
|
+
senior: 136
|
|
120
|
+
|
|
121
|
+
resumes: 23_648
|
|
122
|
+
|
|
123
|
+
java-android:
|
|
124
|
+
salaries:
|
|
125
|
+
entry: >
|
|
126
|
+
$70,000+ (977)
|
|
127
|
+
$85,000+ (785)
|
|
128
|
+
$95,000+ (613)
|
|
129
|
+
$105,000+ (398)
|
|
130
|
+
$115,000+ (235)
|
|
131
|
+
|
|
132
|
+
mid: >
|
|
133
|
+
$85,000+ (2461)
|
|
134
|
+
$95,000+ (2032)
|
|
135
|
+
$105,000+ (1451)
|
|
136
|
+
$110,000+ (1176)
|
|
137
|
+
$120,000+ (646)
|
|
138
|
+
|
|
139
|
+
senior: >
|
|
140
|
+
$90,000+ (896)
|
|
141
|
+
$100,000+ (758)
|
|
142
|
+
$110,000+ (563)
|
|
143
|
+
$120,000+ (343)
|
|
144
|
+
$130,000+ (181)
|
|
145
|
+
|
|
146
|
+
jobs:
|
|
147
|
+
entry: 1_142
|
|
148
|
+
mid: 2_896
|
|
149
|
+
senior: 1_002
|
|
150
|
+
|
|
151
|
+
resumes: 65_373
|
|
152
|
+
|
|
153
|
+
java-j2ee:
|
|
154
|
+
salaries:
|
|
155
|
+
entry: >
|
|
156
|
+
$70,000+ (764)
|
|
157
|
+
$75,000+ (688)
|
|
158
|
+
$85,000+ (487)
|
|
159
|
+
$95,000+ (322)
|
|
160
|
+
$105,000+ (176)
|
|
161
|
+
|
|
162
|
+
mid: >
|
|
163
|
+
$80,000+ (3173)
|
|
164
|
+
$90,000+ (2557)
|
|
165
|
+
$95,000+ (2142)
|
|
166
|
+
$100,000+ (1697)
|
|
167
|
+
$110,000+ (845)
|
|
168
|
+
|
|
169
|
+
senior: >
|
|
170
|
+
$90,000+ (1805)
|
|
171
|
+
$100,000+ (1412)
|
|
172
|
+
$105,000+ (1166)
|
|
173
|
+
$110,000+ (935)
|
|
174
|
+
$120,000+ (510)
|
|
175
|
+
|
|
176
|
+
jobs:
|
|
177
|
+
entry: 910
|
|
178
|
+
mid: 3_692
|
|
179
|
+
senior: 2_087
|
|
180
|
+
|
|
181
|
+
resumes: 92_525
|
|
182
|
+
|
|
183
|
+
swift-ios:
|
|
184
|
+
salaries:
|
|
185
|
+
entry: >
|
|
186
|
+
$65,000+ (403)
|
|
187
|
+
$85,000+ (340)
|
|
188
|
+
$95,000+ (282)
|
|
189
|
+
$105,000+ (196)
|
|
190
|
+
$115,000+ (102)
|
|
191
|
+
|
|
192
|
+
mid: >
|
|
193
|
+
$85,000+ (837)
|
|
194
|
+
$95,000+ (699)
|
|
195
|
+
$105,000+ (502)
|
|
196
|
+
$110,000+ (417)
|
|
197
|
+
$120,000+ (218)
|
|
198
|
+
|
|
199
|
+
senior: >
|
|
200
|
+
$95,000+ (235)
|
|
201
|
+
$105,000+ (189)
|
|
202
|
+
$110,000+ (159)
|
|
203
|
+
$120,000+ (98)
|
|
204
|
+
$130,000+ (47)
|
|
205
|
+
|
|
206
|
+
jobs:
|
|
207
|
+
entry: 471
|
|
208
|
+
mid: 984
|
|
209
|
+
senior: 267
|
|
210
|
+
|
|
211
|
+
resumes: 6_169
|
|
212
|
+
|
|
213
|
+
javascript-jquery:
|
|
214
|
+
salaries:
|
|
215
|
+
entry: >
|
|
216
|
+
$60,000+ (2249)
|
|
217
|
+
$75,000+ (1821)
|
|
218
|
+
$85,000+ (1404)
|
|
219
|
+
$95,000+ (903)
|
|
220
|
+
$105,000+ (453)
|
|
221
|
+
|
|
222
|
+
mid: >
|
|
223
|
+
$75,000+ (8387)
|
|
224
|
+
$85,000+ (6890)
|
|
225
|
+
$90,000+ (5850)
|
|
226
|
+
$100,000+ (3551)
|
|
227
|
+
$110,000+ (1685)
|
|
228
|
+
|
|
229
|
+
senior: >
|
|
230
|
+
$90,000+ (2352)
|
|
231
|
+
$95,000+ (2049)
|
|
232
|
+
$100,000+ (1701)
|
|
233
|
+
$105,000+ (1257)
|
|
234
|
+
$115,000+ (604)
|
|
235
|
+
|
|
236
|
+
jobs:
|
|
237
|
+
entry: 2_571
|
|
238
|
+
mid: 9_641
|
|
239
|
+
senior: 2_803
|
|
240
|
+
|
|
241
|
+
resumes: 123_148
|
|
242
|
+
|
|
243
|
+
php:
|
|
244
|
+
salaries:
|
|
245
|
+
entry: >
|
|
246
|
+
$45,000+ (4548)
|
|
247
|
+
$60,000+ (3761)
|
|
248
|
+
$75,000+ (2779)
|
|
249
|
+
$85,000+ (2058)
|
|
250
|
+
$100,000+ (1100)
|
|
251
|
+
|
|
252
|
+
mid: >
|
|
253
|
+
$65,000+ (9836)
|
|
254
|
+
$80,000+ (7749)
|
|
255
|
+
$90,000+ (5693)
|
|
256
|
+
$95,000+ (4595)
|
|
257
|
+
$105,000+ (2631)
|
|
258
|
+
|
|
259
|
+
senior: >
|
|
260
|
+
$85,000+ (2511)
|
|
261
|
+
$95,000+ (2116)
|
|
262
|
+
$105,000+ (1478)
|
|
263
|
+
$110,000+ (1171)
|
|
264
|
+
$120,000+ (629)
|
|
265
|
+
|
|
266
|
+
jobs:
|
|
267
|
+
entry: 5_458
|
|
268
|
+
mid: 11_397
|
|
269
|
+
senior: 2_892
|
|
270
|
+
|
|
271
|
+
resumes: 184_970
|
|
272
|
+
|
|
273
|
+
javascript-react:
|
|
274
|
+
salaries:
|
|
275
|
+
entry:
|
|
276
|
+
$80,000+ (622)
|
|
277
|
+
$90,000+ (531)
|
|
278
|
+
$100,000+ (380)
|
|
279
|
+
$110,000+ (249)
|
|
280
|
+
$115,000+ (174)
|
|
281
|
+
|
|
282
|
+
mid:
|
|
283
|
+
$90,000+ (1832)
|
|
284
|
+
$95,000+ (1601)
|
|
285
|
+
$100,000+ (1370)
|
|
286
|
+
$110,000+ (825)
|
|
287
|
+
$120,000+ (426)
|
|
288
|
+
|
|
289
|
+
senior:
|
|
290
|
+
$95,000+ (658)
|
|
291
|
+
$105,000+ (505)
|
|
292
|
+
$110,000+ (426)
|
|
293
|
+
$115,000+ (323)
|
|
294
|
+
$125,000+ (158)
|
|
295
|
+
jobs:
|
|
296
|
+
entry: 723
|
|
297
|
+
mid: 2_192
|
|
298
|
+
senior: 727
|
|
299
|
+
resumes: 4_409
|
|
300
|
+
|
|
301
|
+
javascript-angular:
|
|
302
|
+
salaries:
|
|
303
|
+
entry:
|
|
304
|
+
$80,000+ (1233)
|
|
305
|
+
$90,000+ (1029)
|
|
306
|
+
$95,000+ (848)
|
|
307
|
+
$105,000+ (502)
|
|
308
|
+
$115,000+ (275)
|
|
309
|
+
|
|
310
|
+
mid:
|
|
311
|
+
$85,000+ (4146)
|
|
312
|
+
$95,000+ (3285)
|
|
313
|
+
$100,000+ (2704)
|
|
314
|
+
$105,000+ (2049)
|
|
315
|
+
$115,000+ (1074)
|
|
316
|
+
|
|
317
|
+
senior:
|
|
318
|
+
$95,000+ (1435)
|
|
319
|
+
$100,000+ (1269)
|
|
320
|
+
$105,000+ (1010)
|
|
321
|
+
$115,000+ (568)
|
|
322
|
+
$125,000+ (289)
|
|
323
|
+
|
|
324
|
+
jobs:
|
|
325
|
+
entry: 1_453
|
|
326
|
+
mid: 4_725
|
|
327
|
+
senior: 1_641
|
|
328
|
+
|
|
329
|
+
resumes: 27_324
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Array
|
|
2
|
+
# File activesupport/lib/active_support/core_ext/array/grouping.rb, line 20
|
|
3
|
+
def in_groups_of(number, fill_with = nil)
|
|
4
|
+
if number.to_i <= 0
|
|
5
|
+
raise ArgumentError,
|
|
6
|
+
"Group size must be a positive integer, was #{number.inspect}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
if fill_with == false
|
|
10
|
+
collection = self
|
|
11
|
+
else
|
|
12
|
+
padding = (number - size % number) % number
|
|
13
|
+
collection = dup.concat(Array.new(padding, fill_with))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if block_given?
|
|
17
|
+
collection.each_slice(number) { |slice| yield(slice) }
|
|
18
|
+
else
|
|
19
|
+
collection.each_slice(number).to_a
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
|
2
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
|
3
|
+
|
|
4
|
+
module ActiveSupport
|
|
5
|
+
class HashWithIndifferentAccess < Hash
|
|
6
|
+
def extractable_options?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def with_indifferent_access
|
|
11
|
+
dup
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def nested_under_indifferent_access
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(constructor = {})
|
|
19
|
+
if constructor.respond_to?(:to_hash)
|
|
20
|
+
super()
|
|
21
|
+
update(constructor)
|
|
22
|
+
else
|
|
23
|
+
super(constructor)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default(key = nil)
|
|
28
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
|
29
|
+
self[key]
|
|
30
|
+
else
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.new_from_hash_copying_default(hash)
|
|
36
|
+
hash = hash.to_hash
|
|
37
|
+
new(hash).tap do |new_hash|
|
|
38
|
+
new_hash.default = hash.default
|
|
39
|
+
new_hash.default_proc = hash.default_proc if hash.default_proc
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.[](*args)
|
|
44
|
+
new.merge!(Hash[*args])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
|
48
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
|
49
|
+
|
|
50
|
+
def []=(key, value)
|
|
51
|
+
regular_writer(convert_key(key), convert_value(value, for: :assignment))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
alias_method :store, :[]=
|
|
55
|
+
|
|
56
|
+
def update(other_hash)
|
|
57
|
+
if other_hash.is_a? HashWithIndifferentAccess
|
|
58
|
+
super(other_hash)
|
|
59
|
+
else
|
|
60
|
+
other_hash.to_hash.each_pair do |key, value|
|
|
61
|
+
if block_given? && key?(key)
|
|
62
|
+
value = yield(convert_key(key), self[key], value)
|
|
63
|
+
end
|
|
64
|
+
regular_writer(convert_key(key), convert_value(value))
|
|
65
|
+
end
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
alias_method :merge!, :update
|
|
71
|
+
|
|
72
|
+
def key?(key)
|
|
73
|
+
super(convert_key(key))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
alias_method :include?, :key?
|
|
77
|
+
alias_method :has_key?, :key?
|
|
78
|
+
alias_method :member?, :key?
|
|
79
|
+
|
|
80
|
+
def fetch(key, *extras)
|
|
81
|
+
super(convert_key(key), *extras)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def values_at(*indices)
|
|
85
|
+
indices.collect { |key| self[convert_key(key)] }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def dup
|
|
89
|
+
self.class.new(self).tap do |new_hash|
|
|
90
|
+
set_defaults(new_hash)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def merge(hash, &block)
|
|
95
|
+
self.dup.update(hash, &block)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reverse_merge(other_hash)
|
|
99
|
+
super(self.class.new_from_hash_copying_default(other_hash))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def reverse_merge!(other_hash)
|
|
103
|
+
replace(reverse_merge( other_hash ))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def replace(other_hash)
|
|
107
|
+
super(self.class.new_from_hash_copying_default(other_hash))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def delete(key)
|
|
111
|
+
super(convert_key(key))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def stringify_keys!; self end
|
|
115
|
+
def deep_stringify_keys!; self end
|
|
116
|
+
def stringify_keys; dup end
|
|
117
|
+
def deep_stringify_keys; dup end
|
|
118
|
+
undef :symbolize_keys!
|
|
119
|
+
undef :deep_symbolize_keys!
|
|
120
|
+
def symbolize_keys; to_hash.symbolize_keys! end
|
|
121
|
+
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
|
|
122
|
+
def to_options!; self end
|
|
123
|
+
|
|
124
|
+
def select(*args, &block)
|
|
125
|
+
dup.tap { |hash| hash.select!(*args, &block) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def reject(*args, &block)
|
|
129
|
+
dup.tap { |hash| hash.reject!(*args, &block) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def to_hash
|
|
133
|
+
_new_hash = Hash.new
|
|
134
|
+
set_defaults(_new_hash)
|
|
135
|
+
|
|
136
|
+
each do |key, value|
|
|
137
|
+
_new_hash[key] = convert_value(value, for: :to_hash)
|
|
138
|
+
end
|
|
139
|
+
_new_hash
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
protected
|
|
143
|
+
def convert_key(key)
|
|
144
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def convert_value(value, options = {})
|
|
148
|
+
if value.is_a? Hash
|
|
149
|
+
if options[:for] == :to_hash
|
|
150
|
+
value.to_hash
|
|
151
|
+
else
|
|
152
|
+
value.nested_under_indifferent_access
|
|
153
|
+
end
|
|
154
|
+
elsif value.is_a?(Array)
|
|
155
|
+
if options[:for] != :assignment || value.frozen?
|
|
156
|
+
value = value.dup
|
|
157
|
+
end
|
|
158
|
+
value.map! { |e| convert_value(e, options) }
|
|
159
|
+
else
|
|
160
|
+
value
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def set_defaults(target)
|
|
165
|
+
if default_proc
|
|
166
|
+
target.default_proc = default_proc.dup
|
|
167
|
+
else
|
|
168
|
+
target.default = default
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
|
|
174
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), './core_ext/array.rb')
|
|
2
|
+
|
|
3
|
+
module JobDollars
|
|
4
|
+
# This module contains the estimation code.
|
|
5
|
+
module Estimator
|
|
6
|
+
# Estimate value of a developer resume item.
|
|
7
|
+
#
|
|
8
|
+
# @param y (Fixnum) number of years
|
|
9
|
+
# @param hash (HashWithIndifferentAccess) job data for the chosen skill,
|
|
10
|
+
# shaped like the top-level values in langval.yml
|
|
11
|
+
# @return (Float) Value of resume item in dollars.
|
|
12
|
+
def value_of_resume_item_in_dollars(y, hash)
|
|
13
|
+
resu = hash[:resumes]
|
|
14
|
+
sala = crunch_salaries(hash)
|
|
15
|
+
|
|
16
|
+
chance = {
|
|
17
|
+
entry: chance_of_getting_job(y, resu, :entry),
|
|
18
|
+
mid: chance_of_getting_job(y, resu, :mid),
|
|
19
|
+
senior: chance_of_getting_job(y, resu, :senior)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
((chance[:entry] * sala[:entry]) +
|
|
23
|
+
(chance[:mid] * sala[:mid]) +
|
|
24
|
+
(chance[:senior] * sala[:senior])) / 3.0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Estimate the work the job market does for a candidate with a certain
|
|
28
|
+
# number of years of experience in a given skill.
|
|
29
|
+
#
|
|
30
|
+
# @param y (Fixnum) number of years
|
|
31
|
+
# @param hash (HashWithIndifferentAccess) job data for the chosen skill,
|
|
32
|
+
# shaped like the top-level values in langval.yml
|
|
33
|
+
# @return (Float) Market work estimate in job-dollars.
|
|
34
|
+
def market_work_in_job_dollars(y, hash)
|
|
35
|
+
candidate_type = case
|
|
36
|
+
when y >= 5
|
|
37
|
+
:senior
|
|
38
|
+
when y >= 3 && y < 5
|
|
39
|
+
:mid
|
|
40
|
+
when y < 3
|
|
41
|
+
:entry
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
value_of_resume_item_in_dollars(y, hash) * hash[:jobs][candidate_type]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Estimate the work the job market does for a candidate with a certain
|
|
48
|
+
# number of years of experience in a given skill, in Javagrads.
|
|
49
|
+
#
|
|
50
|
+
# @param y (Fixnum) number of years
|
|
51
|
+
# @param hash (HashWithIndifferentAccess) job data for the chosen skill,
|
|
52
|
+
# shaped like the top-level values in langval.yml
|
|
53
|
+
# @return (Float) Market work estimate in Javagrads.
|
|
54
|
+
def market_work_in_javagrads(y, hash)
|
|
55
|
+
market_work_in_job_dollars(y, hash) / JAVAGRAD_IN_JOB_DOLLARS
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Calculate the number of Javagrads of work the job market does for beginners
|
|
59
|
+
# (zero years of experience) with this skill.
|
|
60
|
+
#
|
|
61
|
+
# @param hash (HashWithIndifferentAccess) job data for the chosen skill,
|
|
62
|
+
# shaped like the top-level values in langval.yml
|
|
63
|
+
# @return (Float) Beginner rating in Javagrads.
|
|
64
|
+
def beginner_javagrad_rating(hash)
|
|
65
|
+
market_work_in_javagrads(0.0, hash)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get the average salary for jobs returned from an Indeed.com search,
|
|
69
|
+
# based on refinements sidebar text with facet totals.
|
|
70
|
+
#
|
|
71
|
+
# @param salatext (String) refinements text
|
|
72
|
+
# @param total (Fixnum) total number of results
|
|
73
|
+
# @return (Float) Average salary in dollars
|
|
74
|
+
def average_salary_from_indeed_facets(salatext, total)
|
|
75
|
+
salaries = salatext.split(/\s+/).in_groups_of(2).map { |r| r.map { |i| i.gsub(/\D/, '').to_f }}
|
|
76
|
+
|
|
77
|
+
salaries = [[salaries[0][0], total.to_f]] + salaries
|
|
78
|
+
|
|
79
|
+
salaries = salaries.map.with_index do |salary, idx|
|
|
80
|
+
if idx == 0
|
|
81
|
+
amt, count = *salary
|
|
82
|
+
[amt, count - salaries[1][1]]
|
|
83
|
+
elsif idx == salaries.size-1
|
|
84
|
+
salary
|
|
85
|
+
else
|
|
86
|
+
amt, count = *salary
|
|
87
|
+
[(amt+salaries[idx+1][0])/2, count-salaries[idx+1][1]]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
salaries = salaries.map do |amt, count|
|
|
92
|
+
amt * count
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
salaries.inject(&:+) / total
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
NORMAL_DISTRIBUTION = {
|
|
99
|
+
1 => [1.0],
|
|
100
|
+
2 => [0.5,0.5],
|
|
101
|
+
3 => [0.0918,0.82,0.0918],
|
|
102
|
+
4 => [0.0228,0.48,0.48,0.0228],
|
|
103
|
+
5 => [0.0082,0.21,0.58,0.21,0.0082],
|
|
104
|
+
6 => [0.0038,0.09,0.41,0.41,0.09,0.0038],
|
|
105
|
+
7 => [0.0021,0.04,0.24,0.43,0.24,0.04,0.0021],
|
|
106
|
+
8 => [0.0013,0.02,0.14,0.34,0.34,0.14,0.02,0.0013],
|
|
107
|
+
9 => [0.0009,0.01,0.09,0.24,0.34,0.24,0.09,0.01,0.0009],
|
|
108
|
+
10 => [0.0007,0.01,0.04,0.16,0.29,0.29,0.16,0.04,0.01,0.0007],
|
|
109
|
+
11 => [0.0005,0.01,0.03,0.1,0.22,0.28,0.22,0.1,0.03,0.01,0.0005]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Given the total number of resumes, model the probability
|
|
113
|
+
# distribution that various numbers of resumes will be selected.
|
|
114
|
+
#
|
|
115
|
+
# @param total_resumes (Fixnum) Total number of resumes.
|
|
116
|
+
# @return (Hash<Fixnum, Float>) Probabilities keyed to the number
|
|
117
|
+
# of resumes selected.
|
|
118
|
+
def applicants_per_job_distribution(total_resumes)
|
|
119
|
+
max = (Math.log(total_resumes) / 1.5).to_i + 1
|
|
120
|
+
|
|
121
|
+
dist = (1..max).to_a.map do |num|
|
|
122
|
+
[num, NORMAL_DISTRIBUTION[max][num-1]]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Hash[dist]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Compute the chance a candidate will get a job, given the years
|
|
129
|
+
# of experience, total number of resumes, and the candidate type
|
|
130
|
+
#
|
|
131
|
+
# @param years (Float) candidate years of experience
|
|
132
|
+
# @param total_resumes (Fixnum) total resumes
|
|
133
|
+
# @param type (Symbol) in [:entry, :mid, :senior]
|
|
134
|
+
# @return (Float) Chance the candidate will get a job
|
|
135
|
+
def chance_of_getting_job(years, total_resumes, type = :senior)
|
|
136
|
+
year_range = case
|
|
137
|
+
when type == :senior
|
|
138
|
+
[5.0, 10.0]
|
|
139
|
+
when type == :mid
|
|
140
|
+
[3.0, 5.0]
|
|
141
|
+
when type == :entry
|
|
142
|
+
[0.0, 3.0]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
strength = [((years.to_f - year_range[0]) / (year_range[1] - year_range[0])), 1.0].min
|
|
146
|
+
|
|
147
|
+
return 0.0 if strength < 0
|
|
148
|
+
|
|
149
|
+
dist = applicants_per_job_distribution(total_resumes)
|
|
150
|
+
dist = dist.map do |resumes, c|
|
|
151
|
+
other_resumes = resumes - 1
|
|
152
|
+
|
|
153
|
+
# probability of getting the job
|
|
154
|
+
c * (strength ** other_resumes)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
dist.inject(&:+)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Get the salary distribution for a skill.
|
|
161
|
+
#
|
|
162
|
+
# @param hash (Hash) A hash of job data shaped like the top-level values
|
|
163
|
+
# in langval.yml
|
|
164
|
+
# @return (Hash<Symbol, Float>) {entry: Float, mid: Float, senior: Float}
|
|
165
|
+
def crunch_salaries(hash)
|
|
166
|
+
{
|
|
167
|
+
entry: average_salary_from_indeed_facets(hash["salaries"]["entry"], hash["jobs"]["entry"]),
|
|
168
|
+
mid: average_salary_from_indeed_facets(hash["salaries"]["mid"], hash["jobs"]["mid"]),
|
|
169
|
+
senior: average_salary_from_indeed_facets(hash["salaries"]["senior"], hash["jobs"]["senior"])
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Convert job-dollars to Javagrads.
|
|
174
|
+
#
|
|
175
|
+
# @param job_dollars (Float) Job-dollars.
|
|
176
|
+
# @return (Float) Javagrads, precision-1.
|
|
177
|
+
def to_javagrads(job_dollars)
|
|
178
|
+
(job_dollars / JAVAGRAD).to_f(1)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
|
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: job_dollars
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.5
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tyler Boyd
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-08-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A job dollar is an objective measure of the strength of the job market
|
|
14
|
+
for entry-level workers using a given programming language and framework. It is
|
|
15
|
+
a composite measure of work, like kilowatt-hours or Joules, and it measures how
|
|
16
|
+
much work the market has done for you.
|
|
17
|
+
email: tboyd47@gmail.com
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- lib/job_dollars.rb
|
|
23
|
+
- lib/job_dollars.yml
|
|
24
|
+
- lib/job_dollars/core_ext/array.rb
|
|
25
|
+
- lib/job_dollars/core_ext/hash.rb
|
|
26
|
+
- lib/job_dollars/core_ext/hash_with_indifferent_access.rb
|
|
27
|
+
- lib/job_dollars/estimator.rb
|
|
28
|
+
homepage: https://www.github.com/tb0yd/job_dollars
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata: {}
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options:
|
|
34
|
+
- "-x"
|
|
35
|
+
- lib/job_dollars/core_ext/*
|
|
36
|
+
- "-m"
|
|
37
|
+
- README.md
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
requirements: []
|
|
51
|
+
rubyforge_project:
|
|
52
|
+
rubygems_version: 2.4.5.1
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: An economic measure of the value of a programming language and framework.
|
|
56
|
+
test_files: []
|