roth_ira 1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a7bbd77d6a3283c63e435ca850c4d6935621f37
4
+ data.tar.gz: 01be66098e85fb161399ca7b43b56f68ee61b04d
5
+ SHA512:
6
+ metadata.gz: 5637387863f8e81764a364750842fa8685ad9c014252c64ce2c4e2620b9239ca620ec832fe32e8271c8158864238afea10881908a045a6b9a4d516fcdf194d2b
7
+ data.tar.gz: c6450b1b50a392c65bca6039076b4ea759946acc7953d133ded5236dcde46601e9631f18066830b1771911549a2353a8bf05a99a6251c8f712af5c60948c2381
data/lib/limits.yaml ADDED
@@ -0,0 +1,55 @@
1
+ ---
2
+ 2015:
3
+ :single:
4
+ :lower_income_limit: 116000
5
+ :upper_income_limit: 131000
6
+ :contribution_limit: 5500
7
+ :minimum_phase_out: 200
8
+ :head_of_household:
9
+ :lower_income_limit: 116000
10
+ :upper_income_limit: 131000
11
+ :contribution_limit: 5500
12
+ :minimum_phase_out: 200
13
+ :married_filing_jointly:
14
+ :lower_income_limit: 183000
15
+ :upper_income_limit: 193000
16
+ :contribution_limit: 11000
17
+ :minimum_phase_out: 400
18
+ :catch_up_contribution: 1000
19
+ :catch_up_age: 50
20
+ 2016:
21
+ :single:
22
+ :lower_income_limit: 117000
23
+ :upper_income_limit: 132000
24
+ :contribution_limit: 5500
25
+ :minimum_phase_out: 200
26
+ :head_of_household:
27
+ :lower_income_limit: 117000
28
+ :upper_income_limit: 132000
29
+ :contribution_limit: 5500
30
+ :minimum_phase_out: 200
31
+ :married_filing_jointly:
32
+ :lower_income_limit: 184000
33
+ :upper_income_limit: 194000
34
+ :contribution_limit: 11000
35
+ :minimum_phase_out: 400
36
+ :catch_up_contribution: 1000
37
+ :catch_up_age: 50
38
+ 2017:
39
+ :single:
40
+ :lower_income_limit: 118000
41
+ :upper_income_limit: 133000
42
+ :contribution_limit: 5500
43
+ :minimum_phase_out: 200
44
+ :head_of_household:
45
+ :lower_income_limit: 118000
46
+ :upper_income_limit: 133000
47
+ :contribution_limit: 5500
48
+ :minimum_phase_out: 200
49
+ :married_filing_jointly:
50
+ :lower_income_limit: 186000
51
+ :upper_income_limit: 196000
52
+ :contribution_limit: 11000
53
+ :minimum_phase_out: 400
54
+ :catch_up_contribution: 1000
55
+ :catch_up_age: 50
data/lib/roth_ira.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'yaml'
2
+
3
+ class RothIRA
4
+ attr_reader :age, :agi, :filing_status, :limits, :spouse_age, :year
5
+
6
+ def initialize(year)
7
+ @year = year
8
+ @limits = YAML.load(File.read(__dir__ + "/limits.yaml"))[year]
9
+ end
10
+
11
+ def calculate(agi, filing_status, *ages)
12
+ @agi = agi
13
+ @filing_status = filing_status
14
+ @age = ages.first
15
+ @spouse_age = ages.size == 2 ? ages.last : 0
16
+
17
+ calculate_limit
18
+ end
19
+
20
+ private
21
+
22
+ def calculate_limit
23
+ case agi
24
+ when 0..max_contribution then agi
25
+ when max_contribution..lower_limit then max_contribution
26
+ when lower_limit...upper_limit then calculate_phase_out
27
+ else 0
28
+ end
29
+ end
30
+
31
+ def lower_limit
32
+ limits[filing_status][:lower_income_limit]
33
+ end
34
+
35
+ def upper_limit
36
+ limits[filing_status][:upper_income_limit]
37
+ end
38
+
39
+ def max_contribution
40
+ contribution_limit + calculate_catch_up
41
+ end
42
+
43
+ def catch_up_age
44
+ limits[:catch_up_age]
45
+ end
46
+
47
+ def catch_up_contribution
48
+ limits[:catch_up_contribution]
49
+ end
50
+
51
+ def contribution_limit
52
+ limits[filing_status][:contribution_limit]
53
+ end
54
+
55
+ def minimum_phase_out
56
+ limits[filing_status][:minimum_phase_out]
57
+ end
58
+
59
+ def calculate_catch_up
60
+ eligible_for_catch_up = [primary_eligible_for_catch_up, spouse_eligible_for_catch_up]
61
+ eligible_for_catch_up.count(true) * catch_up_contribution
62
+ end
63
+
64
+ def primary_eligible_for_catch_up
65
+ age >= catch_up_age
66
+ end
67
+
68
+ def spouse_eligible_for_catch_up
69
+ filing_status == :married_filing_jointly && spouse_age >= catch_up_age
70
+ end
71
+
72
+ def calculate_phase_out
73
+ excess_income = (agi - lower_limit).to_f
74
+ ratio = 1 - (excess_income/limit_range).round(3)
75
+ phase_out_amount = nearest_10(max_contribution * ratio)
76
+ minimum_200 phase_out_amount
77
+ end
78
+
79
+ def limit_range
80
+ upper_limit - lower_limit
81
+ end
82
+
83
+ def nearest_10(amount)
84
+ # IRS instructions are to round contribution limit UP to the nearest $10
85
+ (amount.to_f / 10).ceil * 10
86
+ end
87
+
88
+ def minimum_200(amount)
89
+ # IRS instructions allow minimum contribution of $200 in phase-out range (per person)
90
+ amount < minimum_phase_out ? minimum_phase_out : amount
91
+ end
92
+ end
@@ -0,0 +1,3 @@
1
+ class RothIRA
2
+ VERSION = '1.2.0'
3
+ end
@@ -0,0 +1,314 @@
1
+ require 'spec_helper'
2
+
3
+ describe RothIRA do
4
+ it 'should return correct version string' do
5
+ expect(RothIRA::VERSION).to eq('1.2.0')
6
+ end
7
+
8
+ context 'Tax year 2015' do
9
+ let(:roth_ira) { RothIRA.new(2015) }
10
+ context 'Single' do
11
+ it 'should return correct value for low income single' do
12
+ expect(roth_ira.calculate(1500, :single, 25)).to eq(1500)
13
+ end
14
+
15
+ it 'should return correct value for moderate income single' do
16
+ expect(roth_ira.calculate(75000, :single, 25)).to eq(5500)
17
+ end
18
+
19
+ it 'should return correct value for high income single' do
20
+ expect(roth_ira.calculate(135000, :single, 25)).to eq(0)
21
+ end
22
+
23
+ it 'should return correct value for phase-out income single' do
24
+ expect(roth_ira.calculate(122800, :single, 25)).to eq(3010)
25
+ end
26
+
27
+ it 'should correctly handle minimum phase out amount of $200' do
28
+ expect(roth_ira.calculate(130900, :single, 25)).to eq(200)
29
+ end
30
+
31
+ context 'over 50' do
32
+ it 'should return correct value for low income single over 50' do
33
+ expect(roth_ira.calculate(1500, :single, 50)).to eq(1500)
34
+ end
35
+
36
+ it 'should return correct value for moderate income single over 50' do
37
+ expect(roth_ira.calculate(75000, :single, 50)).to eq(6500)
38
+ end
39
+
40
+ it 'should return correct value for phase-out income single over 50' do
41
+ expect(roth_ira.calculate(121600, :single, 50)).to eq(4080)
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'Head of Household' do
47
+ it 'should return correct value for low income head of household' do
48
+ expect(roth_ira.calculate(1500, :head_of_household, 25)).to eq(1500)
49
+ end
50
+
51
+ it 'should return correct value for moderate income head of household' do
52
+ expect(roth_ira.calculate(75000, :head_of_household, 25)).to eq(5500)
53
+ end
54
+
55
+ it 'should return correct value for high income head of household' do
56
+ expect(roth_ira.calculate(135000, :head_of_household, 25)).to eq(0)
57
+ end
58
+
59
+ it 'should return correct value for phase-out income head of household' do
60
+ expect(roth_ira.calculate(122800, :head_of_household, 25)).to eq(3010)
61
+ end
62
+
63
+ it 'should correctly handle minimum phase out amount of $200' do
64
+ expect(roth_ira.calculate(130900, :head_of_household, 25)).to eq(200)
65
+ end
66
+
67
+ context 'over 50' do
68
+ it 'should return correct value for low income head of household over 50' do
69
+ expect(roth_ira.calculate(1500, :head_of_household, 50)).to eq(1500)
70
+ end
71
+
72
+ it 'should return correct value for moderate income head of household over 50' do
73
+ expect(roth_ira.calculate(75000, :head_of_household, 50)).to eq(6500)
74
+ end
75
+
76
+ it 'should return correct value for phase-out income head of household over 50' do
77
+ expect(roth_ira.calculate(121600, :head_of_household, 50)).to eq(4080)
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'Married Filing Jointly' do
83
+ it 'should return correct value for low income married filing jointly' do
84
+ expect(roth_ira.calculate(9900, :married_filing_jointly, 25, 26)).to eq(9900)
85
+ end
86
+
87
+ it 'should return correct value for moderate income married filing jointly' do
88
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 25, 26)).to eq(11000)
89
+ end
90
+
91
+ it 'should return correct value for high income married filing jointly' do
92
+ expect(roth_ira.calculate(195000, :married_filing_jointly, 25, 26)).to eq(0)
93
+ end
94
+
95
+ it 'should return correct value for phase-out income married filing jointly' do
96
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 25, 26)).to eq(3850)
97
+ end
98
+
99
+ context 'over 50' do
100
+ it 'should return correct value for low income married filing jointly, with one over 50' do
101
+ expect(roth_ira.calculate(9900, :married_filing_jointly, 50, 49)).to eq(9900)
102
+ end
103
+
104
+ it 'should return correct value for moderate income married filing jointly, with one over 50' do
105
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 50, 49)).to eq(12000)
106
+ end
107
+
108
+ it 'should return correct value for moderate income married filing jointly, with both over 50' do
109
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 50, 51)).to eq(13000)
110
+ end
111
+
112
+ it 'should return correct value for phase-out income married filing jointly, with one over 50' do
113
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 50, 49)).to eq(4200)
114
+ end
115
+
116
+ it 'should return correct value for phase-out income married filing jointly, with both over 50' do
117
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 50, 65)).to eq(4550)
118
+ end
119
+
120
+ it 'should return correct value for high income married filing jointly, with both over 50' do
121
+ expect(roth_ira.calculate(195000, :married_filing_jointly, 55, 62)).to eq(0)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'Tax year 2016' do
128
+ let(:roth_ira) { RothIRA.new(2016) }
129
+ context 'Single' do
130
+ it 'should return correct value for low income single' do
131
+ expect(roth_ira.calculate(1500, :single, 25)).to eq(1500)
132
+ end
133
+
134
+ it 'should return correct value for moderate income single' do
135
+ expect(roth_ira.calculate(75000, :single, 25)).to eq(5500)
136
+ end
137
+
138
+ it 'should return correct value for high income single' do
139
+ expect(roth_ira.calculate(135000, :single, 25)).to eq(0)
140
+ end
141
+
142
+ it 'should return correct value for phase-out income single' do
143
+ expect(roth_ira.calculate(122800, :single, 25)).to eq(3380)
144
+ end
145
+
146
+ it 'should correctly handle minimum phase out amount of $200' do
147
+ expect(roth_ira.calculate(131900, :single, 25)).to eq(200)
148
+ end
149
+
150
+ context 'over 50' do
151
+ it 'should return correct value for low income single over 50' do
152
+ expect(roth_ira.calculate(1500, :single, 50)).to eq(1500)
153
+ end
154
+
155
+ it 'should return correct value for moderate income single over 50' do
156
+ expect(roth_ira.calculate(75000, :single, 50)).to eq(6500)
157
+ end
158
+
159
+ it 'should return correct value for phase-out income single over 50' do
160
+ expect(roth_ira.calculate(121600, :single, 50)).to eq(4510)
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'Head of Household' do
166
+ it 'should return correct value for low income head of household' do
167
+ expect(roth_ira.calculate(1500, :head_of_household, 25)).to eq(1500)
168
+ end
169
+
170
+ it 'should return correct value for moderate income head of household' do
171
+ expect(roth_ira.calculate(75000, :head_of_household, 25)).to eq(5500)
172
+ end
173
+
174
+ it 'should return correct value for high income head of household' do
175
+ expect(roth_ira.calculate(135000, :head_of_household, 25)).to eq(0)
176
+ end
177
+
178
+ it 'should return correct value for phase-out income head of household' do
179
+ expect(roth_ira.calculate(122800, :head_of_household, 25)).to eq(3380)
180
+ end
181
+
182
+ it 'should correctly handle minimum phase out amount of $200' do
183
+ expect(roth_ira.calculate(131900, :head_of_household, 25)).to eq(200)
184
+ end
185
+
186
+ context 'over 50' do
187
+ it 'should return correct value for low income head of household over 50' do
188
+ expect(roth_ira.calculate(1500, :head_of_household, 50)).to eq(1500)
189
+ end
190
+
191
+ it 'should return correct value for moderate income head of household over 50' do
192
+ expect(roth_ira.calculate(75000, :head_of_household, 50)).to eq(6500)
193
+ end
194
+
195
+ it 'should return correct value for phase-out income head of household over 50' do
196
+ expect(roth_ira.calculate(121600, :head_of_household, 50)).to eq(4510)
197
+ end
198
+ end
199
+ end
200
+
201
+ context 'Married Filing Jointly' do
202
+ it 'should return correct value for low income married filing jointly' do
203
+ expect(roth_ira.calculate(9900, :married_filing_jointly, 25, 26)).to eq(9900)
204
+ end
205
+
206
+ it 'should return correct value for moderate income married filing jointly' do
207
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 25, 26)).to eq(11000)
208
+ end
209
+
210
+ it 'should return correct value for high income married filing jointly' do
211
+ expect(roth_ira.calculate(195000, :married_filing_jointly, 25, 26)).to eq(0)
212
+ end
213
+
214
+ it 'should return correct value for phase-out income married filing jointly' do
215
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 25, 26)).to eq(4950)
216
+ end
217
+
218
+ context 'over 50' do
219
+ it 'should return correct value for low income married filing jointly, with one over 50' do
220
+ expect(roth_ira.calculate(9900, :married_filing_jointly, 50, 49)).to eq(9900)
221
+ end
222
+
223
+ it 'should return correct value for moderate income married filing jointly, with one over 50' do
224
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 50, 49)).to eq(12000)
225
+ end
226
+
227
+ it 'should return correct value for moderate income married filing jointly, with both over 50' do
228
+ expect(roth_ira.calculate(135000, :married_filing_jointly, 50, 51)).to eq(13000)
229
+ end
230
+
231
+ it 'should return correct value for phase-out income married filing jointly, with one over 50' do
232
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 50, 49)).to eq(5400)
233
+ end
234
+
235
+ it 'should return correct value for phase-out income married filing jointly, with both over 50' do
236
+ expect(roth_ira.calculate(189500, :married_filing_jointly, 50, 65)).to eq(5850)
237
+ end
238
+
239
+ it 'should return correct value for high income married filing jointly, with both over 50' do
240
+ expect(roth_ira.calculate(195000, :married_filing_jointly, 55, 62)).to eq(0)
241
+ end
242
+
243
+ it 'should correctly handle minimum phase out amount of $200' do
244
+ expect(roth_ira.calculate(193900, :married_filing_jointly, 55, 62)).to eq(400)
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'Tax year 2017' do
251
+ let(:roth_ira) { RothIRA.new(2017) }
252
+ context 'Single' do
253
+ it 'returns maximum contribution for bottom of income limit' do
254
+ expect(roth_ira.calculate(118000, :single, 40)).to eq(5500)
255
+ end
256
+
257
+ it 'returns maximum contribution for bottom of income limit with catch-up' do
258
+ expect(roth_ira.calculate(118000, :single, 50)).to eq(6500)
259
+ end
260
+
261
+ it 'returns zero contribution for top of income limit' do
262
+ expect(roth_ira.calculate(133000, :single, 40)).to eq(0)
263
+ end
264
+
265
+ it 'returns maximum contribution for bottom of income limit with catch-up' do
266
+ expect(roth_ira.calculate(133000, :single, 50)).to eq(0)
267
+ end
268
+ end
269
+
270
+ context 'Head of Household' do
271
+ it 'returns maximum contribution for bottom of income limit' do
272
+ expect(roth_ira.calculate(118000, :head_of_household, 40)).to eq(5500)
273
+ end
274
+
275
+ it 'returns maximum contribution for bottom of income limit with catch-up' do
276
+ expect(roth_ira.calculate(118000, :head_of_household, 50)).to eq(6500)
277
+ end
278
+
279
+ it 'returns zero contribution for top of income limit' do
280
+ expect(roth_ira.calculate(133000, :head_of_household, 40)).to eq(0)
281
+ end
282
+
283
+ it 'returns maximum contribution for bottom of income limit with catch-up' do
284
+ expect(roth_ira.calculate(133000, :head_of_household, 50)).to eq(0)
285
+ end
286
+ end
287
+
288
+ context 'Married Filing Jointly' do
289
+ it 'returns maximum contribution for bottom of income limit' do
290
+ expect(roth_ira.calculate(186000, :married_filing_jointly, 40, 40)).to eq(11000)
291
+ end
292
+
293
+ it 'returns maximum contribution for bottom of income limit with one catch-up' do
294
+ expect(roth_ira.calculate(186000, :married_filing_jointly, 40, 50)).to eq(12000)
295
+ end
296
+
297
+ it 'returns maximum contribution for bottom of income limit with both catch-up' do
298
+ expect(roth_ira.calculate(186000, :married_filing_jointly, 50, 50)).to eq(13000)
299
+ end
300
+
301
+ it 'returns zero contribution for top of income limit' do
302
+ expect(roth_ira.calculate(196000, :married_filing_jointly, 40, 40)).to eq(0)
303
+ end
304
+
305
+ it 'returns maximum contribution for bottom of income limit with one catch-up' do
306
+ expect(roth_ira.calculate(196000, :married_filing_jointly, 40, 50)).to eq(0)
307
+ end
308
+
309
+ it 'returns maximum contribution for bottom of income limit with both catch-up' do
310
+ expect(roth_ira.calculate(196000, :married_filing_jointly, 50, 50)).to eq(0)
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'byebug'
5
+ require 'roth_ira'
6
+
7
+ RSpec.configure do |config|
8
+ config.filter_run :focus
9
+ config.run_all_when_everything_filtered = true
10
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roth_ira
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Randall Reed
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.3'
27
+ description: Determines max Roth IRA contribution amount based on year, filing status,
28
+ and MAGI
29
+ email:
30
+ - randallreedjr@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/limits.yaml
36
+ - lib/roth_ira.rb
37
+ - lib/roth_ira/version.rb
38
+ - spec/roth_ira_spec.rb
39
+ - spec/spec_helper.rb
40
+ homepage: http://github.com/randallreedjr/roth_ira
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.4.8
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Roth IRA Contribution Limit Calculator
64
+ test_files:
65
+ - spec/roth_ira_spec.rb
66
+ - spec/spec_helper.rb