Skip to content

Commit 5d2d163

Browse files
committed
Add 2024 forms.
1 parent 6820b2a commit 5d2d163

29 files changed

+1675
-23
lines changed

2023/example_joint_return.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
# of 100k. Also, the first spouse has some investment income and
55
# business (Schedule C) income.
66
#
7-
# A balance is owed on the return.
8-
#
9-
# On the state return, a refund is due.
7+
# Refunds are due on both returns.
108
#
119

1210
from f1040 import F1040

2023/example_joint_return_amt.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
# of 100k. Also, the first spouse has some investment income and
55
# business (Schedule C) income.
66
#
7-
# This example increases the amount of state tax paid on Schedule A, causing
7+
# This example includes exercise of some ISOs that cause
88
# this couple to be paying AMT instead of regular tax. Form 6251 is added.
99
#
10-
# A balance is owed on the return.
10+
# If this is not the latest tax year, this will also generate part of Form
11+
# 8801 for the following year which is needed for the AMT credit.
1112
#
12-
# On the state return, a refund is due.
13+
# We compute CA state AMT, but it ends up being zero in this case.
14+
#
15+
# Refunds are due on both returns.
1316
#
1417

1518
from f1040 import F1040
@@ -22,17 +25,17 @@
2225
inputs = {
2326
'status': FilingStatus.JOINT,
2427
'exemptions': 2,
25-
'wages': [120000.00, 120000.00], # W2 box 1
26-
'withholding': 42000.00, # W2 box 2
27-
'wages_ss': [120000.00, 120000.00], # W2 box 3
28+
'wages': [100000.00, 100000.00], # W2 box 1
29+
'withholding': 37000.00, # W2 box 2
30+
'wages_ss': [100000.00, 100000.00], # W2 box 3
2831
'ss_withheld': [ 6200.00, 6200.00], # W2 box 4
29-
'wages_medicare': [120000.00, 120000.00], # W2 box 5
32+
'wages_medicare': [100000.00, 100000.00], # W2 box 5
3033
'medicare_withheld': [ 1450.00, 1450.00], # W2 box 6
31-
'state_withholding': 20000.00, # W2 box 17
34+
'state_withholding': 18000.00, # W2 box 17
3235

3336
# These are other state tax payments made in the tax year not included in
3437
# 'state_withholding' that are deductible on schedule A:
35-
'extra_state_tax_payments': 8000.00,
38+
'extra_state_tax_payments': 3000.00,
3639

3740
'taxable_interest': 1500.00,
3841
'tax_exempt_interest': 700.00,
@@ -46,6 +49,14 @@
4649
'F1040sa' : {
4750
'11' : 500, # charitable contributions
4851
},
52+
53+
# Exercise of ISOs
54+
'F6251' : {
55+
'2i' : 50000, # Excess of AMT income over regular income
56+
},
57+
'CA540sp' : {
58+
'10' : 50000,
59+
},
4960
}
5061

5162
f = F1040(inputs)

2023/example_marginal_rates_joint.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
template = {
2121
'status': FilingStatus.JOINT,
22-
'exemptions': 2,
22+
'exemptions': 4,
23+
'qualifying_children': 2,
2324
'disable_rounding': True,
2425
}
2526

@@ -38,15 +39,15 @@ def compute_with_income(template, income, capital_gains):
3839
return F1040(inputs)
3940

4041

41-
max_income = 700000
42+
max_income = 950000
4243
step = 1000
4344
inc = 10
4445

4546
incomes = []
4647
rates = []
4748
capgain_rates = []
4849

49-
for x in range(0, max_income, step):
50+
for x in range(70000, max_income, step):
5051
fbase = compute_with_income(template, x/2, 0)
5152
fnext = compute_with_income(template, (x + inc) / 2, 0)
5253
fcapgain = compute_with_income(template, x/2, inc)

2023/f1040.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from f6251 import F6251
77
from f8606 import F8606
88
from f8801 import F8801
9-
#from f8801_2024 import F8801_2024
9+
from f8801_2024 import F8801_2024
1010
from f8812 import F8812
1111
from f8959 import F8959
1212
from f8960 import F8960
@@ -152,6 +152,9 @@ def __init__(f, inputs={}):
152152
if foreign_tax:
153153
f['s3_1'] = foreign_tax
154154

155+
# TODO: The second instance of sd passed to F6251 is refigured for AMT.
156+
# If you have a different basis in AMT for some Schedule D items, that
157+
# copy should be different.
155158
f6251 = F6251(inputs, f, sa if sa.mustFile() else None, sd, sd)
156159
f.comment['s2_1'] = 'AMT'
157160
f['s2_1'] = f6251.get('11')
@@ -250,8 +253,8 @@ def __init__(f, inputs={}):
250253
f.comment['37'] = 'Amount you owe'
251254
f['37'] = f['24'] - f['33']
252255

253-
#f8801_2024 = F8801_2024(inputs, f, f6251, f8801, sd)
254-
#f.addForm(f8801_2024)
256+
f8801_2024 = F8801_2024(inputs, f, f6251, f8801, sd)
257+
f.addForm(f8801_2024)
255258

256259
def div_cap_gain_tax_worksheet(f, inputs, sched_d):
257260
w = {}

2023/f8801_2024.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from form import Form, FilingStatus
2+
3+
class F8801_2024(Form):
4+
"""Form 8801, Credit for Prior Year Minimum Tax
5+
6+
This form is for 2024, but since it depends on values
7+
from 2023, it is generated along with 2023's
8+
forms. Line 21 from this form must be manually entered into
9+
2024's inputs dictionary.
10+
"""
11+
def __init__(f, inputs, f1040, f6251, f8801, sched_d):
12+
super(F8801_2024, f).__init__(inputs)
13+
f['1'] = f6251.rowsum(['1', '2e'])
14+
f['2'] = f6251.rowsum(['2a', '2b', '2c', '2d', '2g', '2h'])
15+
# TODO: Line 3, minimum tax credit net operating loss deduction
16+
f['4'] = max(0, f['1'] + f['2'] + f['3'])
17+
assert(inputs['status'] != FilingStatus.SEPARATE or f['4'] <= 831150)
18+
f.comment['15'] = 'Net minimum tax on exclusion items'
19+
if f['4'] == 0:
20+
f['15'] = 0
21+
else:
22+
f['5'] = f6251.EXEMPTIONS[inputs['status']]
23+
f['6'] = f6251.EXEMPT_LIMITS[inputs['status']]
24+
f['7'] = max(0, f['4'] - f['6'])
25+
f['8'] = f['7'] * 0.25
26+
f['9'] = max(0, f['5'] - f['8'])
27+
f['10'] = max(0, f['4'] - f['9'])
28+
if f['10'] == 0:
29+
f['15'] = 0
30+
else:
31+
# TODO: form 2555
32+
if (f1040['7'] and not sched_d.mustFile()) or f1040['3a'] or \
33+
(sched_d['15'] > 0 and sched_d['16'] > 0):
34+
f['27'] = f.get('10')
35+
# TODO: Schedule D tax worksheet
36+
assert(not sched_d['18'] and not sched_d['19'])
37+
cg_worksheet = f1040.div_cap_gain_tax_worksheet(inputs,
38+
sched_d)
39+
f['28'] = cg_worksheet['4']
40+
f['30'] = f.get('28')
41+
f['31'] = min(f['27'], f['30'])
42+
f['32'] = f['27'] - f['31']
43+
f['33'] = f6251.amt(inputs['status'], f['32'])
44+
f['34'] = f1040.CAPGAIN15_LIMITS[inputs['status']]
45+
f['35'] = cg_worksheet['5']
46+
f['36'] = max(0, f['34'] - f['35'])
47+
f['37'] = min(f['27'], f['28'])
48+
f['38'] = min(f['36'], f['37'])
49+
f['39'] = f['37'] - f['38']
50+
f['40'] = f1040.CAPGAIN20_LIMITS[inputs['status']]
51+
f['41'] = f.get('36')
52+
f['42'] = cg_worksheet['5']
53+
f['43'] = f.rowsum(['41', '42'])
54+
f['44'] = max(0, f['40'] - f['43'])
55+
f['45'] = min(f['39'], f['44'])
56+
f['46'] = f['45'] * .15
57+
f['47'] = f.rowsum(['38', '45'])
58+
if f['47'] != f['27']:
59+
f['48'] = f['37'] - f['47']
60+
f['49'] = f['48'] * .20
61+
if f['29']:
62+
f['50'] = f.rowsum(['32', '47', '48'])
63+
f['51'] = f['27'] - f['50']
64+
f['52'] = f['51'] * .25
65+
f['53'] = f.rowsum(['33', '46', '49', '52'])
66+
f['54'] = f6251.amt(inputs['status'], f['27'])
67+
f['55'] = min(f['53'], f['54'])
68+
f['11'] = f['55']
69+
else:
70+
f['11'] = f6251.amt(inputs['status'], f['10'])
71+
# TODO: Form 1116
72+
f['12'] = f1040.get('s3_1')
73+
f.comment['13'] = 'Tentative minimum tax on exclusion items'
74+
f['13'] = f['11'] - f['12']
75+
f['14'] = f6251.get('10')
76+
f.comment['15'] = 'Net minimum tax on exclusion items'
77+
f['15'] = max(0, f['13'] - f['14'])
78+
79+
f['16'] = f6251.get('11')
80+
f['17'] = f.get('15')
81+
f['18'] = f['16'] - f['17']
82+
f.comment['19'] = '2023 credit carryforward'
83+
f['19'] = f8801.get('26')
84+
f.comment['21'] = 'Accum. credit (enter in 2024 inputs as \'prior_amt_credit\')'
85+
f['21'] = max(0, f['18'] + f['19'] + f['20'])
86+
if f['21']:
87+
f.must_file = True
88+
89+
def title(self):
90+
return 'Form 8801 (for 2024 filing)'

2023/f8812.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ def __init__(f, inputs, f1040):
1818
f['9'] = 400000
1919
else:
2020
f['9'] = 200000
21-
f['10'] = math.ceil(max(0, f['3'] - f['9']) / 1000.0) * 1000
21+
if f.disable_rounding:
22+
f['10'] = max(0, f['3'] - f['9'])
23+
else:
24+
f['10'] = math.ceil(max(0, f['3'] - f['9']) / 1000.0) * 1000
2225
f['11'] = f['10'] * .05
2326
f['12'] = max(0, f['8'] - f['11'])
2427

2024/ca540.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from form import Form, FilingStatus
2+
from f1040 import F1040
3+
from ca540sca import CA540sca
4+
from ca540sp import CA540sp
5+
import math
6+
7+
class CA540(Form):
8+
EXEMPTION = 149
9+
DEPENDENT_EXEMPTION = 461
10+
BRACKET_RATES = [.01, .02, .04, .06, .08, .093, .103, .113, .123]
11+
BRACKET_LIMITS = [
12+
[10756, 25499, 40245, 55866, 70606, 360659, 432787, 721314], # SINGLE
13+
[21512, 50998, 80490, 111732, 141212, 721318, 865574, 1442628],# JOINT
14+
[10756, 25499, 40245, 55866, 70606, 360659, 432787, 721314], # SEPARATE
15+
[21527, 51000, 65744, 81364, 96107, 490493, 588593, 980987], # HEAD
16+
[21512, 50998, 80490, 111732, 141212, 721318, 865574, 1442628],# WIDOW
17+
]
18+
MENTAL_HEALTH_LIMIT = 1000000
19+
MENTAL_HEALTH_RATE = .01
20+
21+
def __init__(f, inputs, f1040):
22+
super(CA540, f).__init__(inputs)
23+
f.must_file = True
24+
f.addForm(f)
25+
26+
for i in f1040.forms:
27+
if i.__class__.__name__ == 'F1040sa':
28+
f1040sa = i
29+
30+
if inputs['status'] in [FilingStatus.JOINT, FilingStatus.WIDOW]:
31+
personal = 2
32+
else:
33+
personal = 1
34+
35+
f['7'] = personal * f.EXEMPTION
36+
# TODO: blind or senior
37+
f['10'] = (inputs['exemptions'] - personal) * f.DEPENDENT_EXEMPTION
38+
f['11'] = f.rowsum(['7', '8', '9', '10'])
39+
40+
# Line 12 is just informational. Leave it out of our computations.
41+
f.comment['13'] = 'Federal AGI'
42+
f['13'] = f1040['11']
43+
sca = f.addForm(CA540sca(inputs, f1040, f1040sa))
44+
f['14'] = sca['1C_B27']
45+
f['15'] = f['13'] - f['14']
46+
f['16'] = sca['1C_C27']
47+
f.comment['17'] = 'CA AGI'
48+
f['17'] = f['15'] + f['16']
49+
f['18'] = sca['2_30']
50+
f.comment['19'] = 'Taxable income'
51+
f['19'] = max(0, f['17'] - f['18'])
52+
53+
f.comment['31'] = 'Tax'
54+
f['31'] = f.tax_rate_schedule(inputs['status'], f['19'])
55+
f['32'] = f.agi_limitation_worksheet(inputs['status'])
56+
f['33'] = max(0, f['31'] - f['32'])
57+
f['35'] = f['33'] + f['34']
58+
59+
f['47'] = f.rowsum(['40', '43', '44', '45', '46'])
60+
f['48'] = max(0, f['35'] - f['47'])
61+
62+
sp = f.addForm(CA540sp(inputs, f, sca, f1040, f1040sa))
63+
f['61'] = sp.get('26')
64+
f.comment['61'] = 'AMT'
65+
66+
if f['19'] > f.MENTAL_HEALTH_LIMIT:
67+
f['62'] = (f['19'] - f.MENTAL_HEALTH_LIMIT) * f.MENTAL_HEALTH_RATE
68+
f.comment['64'] = 'Total tax'
69+
f['64'] = f.rowsum(['48', '61', '62', '63'])
70+
71+
f['71'] = inputs.get('state_withholding')
72+
f['72'] = inputs.get('state_estimated_payments')
73+
# TODO: real estate withholding
74+
75+
f.comment['78'] = 'Total payments'
76+
f['78'] = f.rowsum(['71', '72', '73', '75', '76', '77'])
77+
78+
if f['78'] > f['91']:
79+
f['93'] = f['78'] - f['91']
80+
else:
81+
f['94'] = f['91'] - f['78']
82+
83+
if f['93'] > f['92']:
84+
f['95'] = f['93'] - f['92']
85+
else:
86+
f['96'] = f['92'] - f['93']
87+
88+
if f['95'] > f['64']:
89+
f.comment['97'] = 'Refund'
90+
f['97'] = f['95'] - f['64']
91+
else:
92+
f.comment['100'] = 'Tax due'
93+
f['100'] = f['64'] - f['95']
94+
f.comment['111'] = 'Amount you owe'
95+
f['111'] = f.rowsum(['94', '96', '100', '110'])
96+
97+
@classmethod
98+
def tax_rate_schedule(f, status, val):
99+
# TODO: rounding of amounts less than 100000 to match tax table
100+
tax = 0
101+
prev = 0
102+
i = 0
103+
for lim in f.BRACKET_LIMITS[status]:
104+
if val <= lim:
105+
break
106+
tax += f.BRACKET_RATES[i] * (lim - prev)
107+
prev = lim
108+
i += 1
109+
tax += f.BRACKET_RATES[i] * (val - prev)
110+
return tax
111+
112+
def agi_limitation_worksheet(f, status):
113+
LIMITS = [244857, 489719, 244857, 367291, 489719]
114+
w = {}
115+
w['a'] = f['13']
116+
w['b'] = LIMITS[status]
117+
if w['a'] <= w['b']:
118+
return f['11']
119+
w['c'] = w['a'] - w['b']
120+
divisor = 1250.00 if status == FilingStatus.SEPARATE else 2500.00
121+
w['d'] = math.ceil(w['c'] / divisor)
122+
w['e'] = w['d'] * 6
123+
w['f'] = (f['7'] + f['8'] + f['9']) / f.EXEMPTION
124+
w['g'] = w['e'] * w['f']
125+
w['h'] = f['7'] + f['8'] + f['9']
126+
w['i'] = max(0, w['h'] - w['g'])
127+
w['j'] = f['10'] / f.DEPENDENT_EXEMPTION
128+
w['k'] = w['e'] * w['j']
129+
w['l'] = f['10']
130+
w['m'] = max(0, w['l'] - w['k'])
131+
w['n'] = w['i'] + w['m']
132+
return w['n']
133+
134+
def title(f):
135+
return 'CA Form 540'

0 commit comments

Comments
 (0)