taxcalc 4.3.5__py3-none-any.whl → 4.4.1__py3-none-any.whl
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.
- taxcalc/__init__.py +1 -1
- taxcalc/calcfunctions.py +326 -171
- taxcalc/calculator.py +35 -34
- taxcalc/cli/tc.py +6 -7
- taxcalc/consumption.py +14 -9
- taxcalc/data.py +8 -8
- taxcalc/decorators.py +3 -3
- taxcalc/growdiff.py +6 -6
- taxcalc/growfactors.py +1 -1
- taxcalc/parameters.py +91 -47
- taxcalc/policy.py +23 -7
- taxcalc/records.py +1 -0
- taxcalc/records_variables.json +6 -0
- taxcalc/reforms/ext.json +1 -1
- taxcalc/taxcalcio.py +88 -73
- taxcalc/tests/cmpi_cps_expect.txt +6 -6
- taxcalc/tests/cmpi_puf_expect.txt +6 -6
- taxcalc/tests/conftest.py +42 -41
- taxcalc/tests/test_4package.py +47 -3
- taxcalc/tests/test_benefits.py +9 -8
- taxcalc/tests/test_calcfunctions.py +55 -38
- taxcalc/tests/test_calculator.py +14 -10
- taxcalc/tests/test_compare.py +44 -50
- taxcalc/tests/test_compatible_data.py +9 -7
- taxcalc/tests/test_consumption.py +41 -22
- taxcalc/tests/test_cpscsv.py +81 -31
- taxcalc/tests/test_data.py +31 -24
- taxcalc/tests/test_decorators.py +84 -32
- taxcalc/tests/test_growdiff.py +20 -19
- taxcalc/tests/test_growfactors.py +8 -8
- taxcalc/tests/test_parameters.py +54 -58
- taxcalc/tests/test_policy.py +16 -14
- taxcalc/tests/test_puf_var_stats.py +14 -14
- taxcalc/tests/test_pufcsv.py +40 -40
- taxcalc/tests/test_records.py +73 -60
- taxcalc/tests/test_reforms.py +34 -31
- taxcalc/tests/test_responses.py +4 -4
- taxcalc/tests/test_taxcalcio.py +76 -62
- taxcalc/tests/test_utils.py +78 -46
- taxcalc/utils.py +49 -42
- taxcalc/validation/taxsim35/taxsim_emulation.json +1 -5
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/METADATA +23 -11
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/RECORD +47 -47
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/WHEEL +1 -1
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/LICENSE +0 -0
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/entry_points.txt +0 -0
- {taxcalc-4.3.5.dist-info → taxcalc-4.4.1.dist-info}/top_level.txt +0 -0
taxcalc/tests/test_4package.py
CHANGED
@@ -7,11 +7,40 @@ Tests for package existence and dependencies consistency.
|
|
7
7
|
|
8
8
|
import os
|
9
9
|
import re
|
10
|
+
import ast
|
10
11
|
import subprocess
|
11
12
|
import yaml
|
12
13
|
import pytest
|
13
14
|
|
14
15
|
|
16
|
+
def extract_install_requires(setup_py_content):
|
17
|
+
"""
|
18
|
+
Extract the install_requires list from a setup.py file content.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
setup_py_content (str): The full content of the setup.py file
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
list: A list of package requirements
|
25
|
+
"""
|
26
|
+
# Use regex to find the install_requires list
|
27
|
+
match = re.search(
|
28
|
+
r'"install_requires"\s*:\s*\[([^\]]+)\]', setup_py_content, re.DOTALL
|
29
|
+
)
|
30
|
+
if match:
|
31
|
+
# Extract the contents of the list and split into packages
|
32
|
+
packages_str = match.group(1)
|
33
|
+
# Use ast.literal_eval to safely parse the string representations
|
34
|
+
packages = [
|
35
|
+
ast.literal_eval(f'{pkg.strip()}')
|
36
|
+
for pkg in packages_str.split(',')
|
37
|
+
if pkg.strip()
|
38
|
+
]
|
39
|
+
return packages
|
40
|
+
|
41
|
+
return []
|
42
|
+
|
43
|
+
|
15
44
|
@pytest.mark.local
|
16
45
|
def test_for_package_existence():
|
17
46
|
"""
|
@@ -22,7 +51,7 @@ def test_for_package_existence():
|
|
22
51
|
out = subprocess.check_output(['conda', 'list', 'taxcalc']).decode('ascii')
|
23
52
|
envless_out = out.replace('taxcalc-dev', 'environment')
|
24
53
|
if re.search('taxcalc', envless_out) is not None:
|
25
|
-
assert 'taxcalc package
|
54
|
+
assert False, 'ERROR: taxcalc package is installed'
|
26
55
|
|
27
56
|
|
28
57
|
def test_for_consistency(tests_path):
|
@@ -30,6 +59,7 @@ def test_for_consistency(tests_path):
|
|
30
59
|
Ensure that there is consistency between environment.yml dependencies
|
31
60
|
and conda.recipe/meta.yaml requirements.
|
32
61
|
"""
|
62
|
+
# pylint: disable=too-many-locals
|
33
63
|
dev_pkgs = set([
|
34
64
|
'pytest',
|
35
65
|
'pytest-xdist',
|
@@ -38,11 +68,12 @@ def test_for_consistency(tests_path):
|
|
38
68
|
'coverage',
|
39
69
|
"pip",
|
40
70
|
"jupyter-book",
|
71
|
+
"setuptools"
|
41
72
|
])
|
42
73
|
# read conda.recipe/meta.yaml requirements
|
43
74
|
meta_file = os.path.join(tests_path, '..', '..',
|
44
75
|
'conda.recipe', 'meta.yaml')
|
45
|
-
with open(meta_file, 'r') as stream:
|
76
|
+
with open(meta_file, 'r', encoding='utf-8') as stream:
|
46
77
|
meta = yaml.safe_load(stream)
|
47
78
|
bld = set(meta['requirements']['build'])
|
48
79
|
run = set(meta['requirements']['run'])
|
@@ -51,7 +82,7 @@ def test_for_consistency(tests_path):
|
|
51
82
|
# read environment.yml dependencies
|
52
83
|
envr_file = os.path.join(tests_path, '..', '..',
|
53
84
|
'environment.yml')
|
54
|
-
with open(envr_file, 'r') as stream:
|
85
|
+
with open(envr_file, 'r', encoding='utf-8') as stream:
|
55
86
|
envr = yaml.safe_load(stream)
|
56
87
|
|
57
88
|
env = []
|
@@ -65,3 +96,16 @@ def test_for_consistency(tests_path):
|
|
65
96
|
# confirm that extras in env (relative to run) equal the dev_pkgs set
|
66
97
|
extras = env - run
|
67
98
|
assert extras == dev_pkgs
|
99
|
+
# Read the setup.py file and extract the install_requires list
|
100
|
+
setup_file = os.path.join(tests_path, '..', '..',
|
101
|
+
'setup.py')
|
102
|
+
with open(setup_file, 'r', encoding='utf-8') as f:
|
103
|
+
setup_py_content = f.read()
|
104
|
+
setup = set(extract_install_requires(setup_py_content))
|
105
|
+
# confirm that setup.py
|
106
|
+
print("Setup packages = ", setup)
|
107
|
+
print("Meta packages = ", bld)
|
108
|
+
# if package in both, confirm that the version is the same
|
109
|
+
for pkg in setup.intersection(bld):
|
110
|
+
assert pkg in setup
|
111
|
+
assert pkg in bld
|
taxcalc/tests/test_benefits.py
CHANGED
@@ -16,8 +16,9 @@ import os
|
|
16
16
|
import pytest
|
17
17
|
import numpy as np
|
18
18
|
import pandas as pd
|
19
|
-
|
20
|
-
from taxcalc import
|
19
|
+
from taxcalc.policy import Policy
|
20
|
+
from taxcalc.records import Records
|
21
|
+
from taxcalc.calculator import Calculator
|
21
22
|
|
22
23
|
|
23
24
|
@pytest.mark.benefits
|
@@ -33,11 +34,11 @@ def test_benefits(tests_path, cps_fullsample):
|
|
33
34
|
start_year = recs.current_year
|
34
35
|
calc = Calculator(policy=Policy(), records=recs, verbose=False)
|
35
36
|
assert calc.current_year == start_year
|
36
|
-
year_list =
|
37
|
-
bname_list =
|
38
|
-
benamt_list =
|
39
|
-
bencnt_list =
|
40
|
-
benavg_list =
|
37
|
+
year_list = []
|
38
|
+
bname_list = []
|
39
|
+
benamt_list = []
|
40
|
+
bencnt_list = []
|
41
|
+
benavg_list = []
|
41
42
|
for year in range(start_year, 2034 + 1):
|
42
43
|
calc.advance_to_year(year)
|
43
44
|
size = calc.array('XTOT')
|
@@ -46,7 +47,7 @@ def test_benefits(tests_path, cps_fullsample):
|
|
46
47
|
# (head counts include all members of filing unit receiving a benefit,
|
47
48
|
# which means benavg is f.unit benefit amount divided by f.unit size)
|
48
49
|
for bname in benefit_names:
|
49
|
-
ben = calc.array('{}_ben'
|
50
|
+
ben = calc.array(f'{bname}_ben')
|
50
51
|
benamt = round((ben * wght).sum() * 1e-9, 3)
|
51
52
|
bencnt = round((size[ben > 0] * wght[ben > 0]).sum() * 1e-6, 3)
|
52
53
|
benavg = round(benamt / bencnt, 1)
|
@@ -8,10 +8,10 @@ Tests for Tax-Calculator calcfunctions.py logic.
|
|
8
8
|
import os
|
9
9
|
import re
|
10
10
|
import ast
|
11
|
-
from taxcalc import Records # pylint: disable=import-error
|
12
|
-
from taxcalc import calcfunctions
|
13
11
|
import numpy as np
|
14
12
|
import pytest
|
13
|
+
from taxcalc.records import Records
|
14
|
+
from taxcalc import calcfunctions
|
15
15
|
|
16
16
|
|
17
17
|
class GetFuncDefs(ast.NodeVisitor):
|
@@ -23,10 +23,10 @@ class GetFuncDefs(ast.NodeVisitor):
|
|
23
23
|
GetFuncDefs class constructor
|
24
24
|
"""
|
25
25
|
self.fname = ''
|
26
|
-
self.fnames =
|
27
|
-
self.fargs =
|
28
|
-
self.cvars =
|
29
|
-
self.rvars =
|
26
|
+
self.fnames = [] # function name (fname) list
|
27
|
+
self.fargs = {} # lists of function arguments indexed by fname
|
28
|
+
self.cvars = {} # lists of calc vars in function indexed by fname
|
29
|
+
self.rvars = {} # lists of function return vars indexed by fname
|
30
30
|
|
31
31
|
def visit_Module(self, node): # pylint: disable=invalid-name
|
32
32
|
"""
|
@@ -41,10 +41,10 @@ class GetFuncDefs(ast.NodeVisitor):
|
|
41
41
|
"""
|
42
42
|
self.fname = node.name
|
43
43
|
self.fnames.append(self.fname)
|
44
|
-
self.fargs[self.fname] =
|
44
|
+
self.fargs[self.fname] = []
|
45
45
|
for anode in ast.iter_child_nodes(node.args):
|
46
46
|
self.fargs[self.fname].append(anode.arg)
|
47
|
-
self.cvars[self.fname] =
|
47
|
+
self.cvars[self.fname] = []
|
48
48
|
for bodynode in node.body:
|
49
49
|
if isinstance(bodynode, ast.Return):
|
50
50
|
continue # skip function's Return node
|
@@ -84,8 +84,10 @@ def test_calc_and_used_vars(tests_path):
|
|
84
84
|
"""
|
85
85
|
# pylint: disable=too-many-locals
|
86
86
|
funcpath = os.path.join(tests_path, '..', 'calcfunctions.py')
|
87
|
+
with open(funcpath, 'r', encoding='utf-8') as funcfile:
|
88
|
+
funcfile_text = funcfile.read()
|
87
89
|
gfd = GetFuncDefs()
|
88
|
-
fnames, fargs, cvars, rvars = gfd.visit(ast.parse(
|
90
|
+
fnames, fargs, cvars, rvars = gfd.visit(ast.parse(funcfile_text))
|
89
91
|
# Test (1):
|
90
92
|
# .. create set of vars that are actually calculated in calcfunctions.py
|
91
93
|
all_cvars = set()
|
@@ -106,7 +108,7 @@ def test_calc_and_used_vars(tests_path):
|
|
106
108
|
'in calcfunctions.py\n')
|
107
109
|
for var in records_varinfo.CALCULATED_VARS - all_cvars:
|
108
110
|
found_error1 = True
|
109
|
-
msg1 += 'VAR NOT CALCULATED: {}\n'
|
111
|
+
msg1 += f'VAR NOT CALCULATED: {var}\n'
|
110
112
|
# Test (2):
|
111
113
|
faux_functions = ['EITCamount', 'ComputeBenefit', 'BenefitPrograms',
|
112
114
|
'BenefitSurtax', 'BenefitLimitation']
|
@@ -119,10 +121,10 @@ def test_calc_and_used_vars(tests_path):
|
|
119
121
|
if not crvars_set <= set(fargs[fname]):
|
120
122
|
found_error2 = True
|
121
123
|
for var in crvars_set - set(fargs[fname]):
|
122
|
-
msg2 += 'FUNCTION,VARIABLE: {} {}\n'
|
124
|
+
msg2 += f'FUNCTION,VARIABLE: {fname} {var}\n'
|
123
125
|
# Report errors for the two tests:
|
124
126
|
if found_error1 and found_error2:
|
125
|
-
raise ValueError('{}\n{}'
|
127
|
+
raise ValueError(f'{msg1}\n{msg2}')
|
126
128
|
if found_error1:
|
127
129
|
raise ValueError(msg1)
|
128
130
|
if found_error2:
|
@@ -135,7 +137,7 @@ def test_function_args_usage(tests_path):
|
|
135
137
|
function body.
|
136
138
|
"""
|
137
139
|
funcfilename = os.path.join(tests_path, '..', 'calcfunctions.py')
|
138
|
-
with open(funcfilename, 'r') as funcfile:
|
140
|
+
with open(funcfilename, 'r', encoding='utf-8') as funcfile:
|
139
141
|
fcontent = funcfile.read()
|
140
142
|
fcontent = re.sub('#.*', '', fcontent) # remove all '#...' comments
|
141
143
|
fcontent = re.sub('\n', ' ', fcontent) # replace EOL character with space
|
@@ -149,7 +151,7 @@ def test_function_args_usage(tests_path):
|
|
149
151
|
msg = ('Could not find function name, arguments, '
|
150
152
|
'and code portions in the following text:\n')
|
151
153
|
msg += '--------------------------------------------------------\n'
|
152
|
-
msg += '{}\n'
|
154
|
+
msg += f'{fcode}\n'
|
153
155
|
msg += '--------------------------------------------------------\n'
|
154
156
|
raise ValueError(msg)
|
155
157
|
fname = match.group(1)
|
@@ -161,11 +163,14 @@ def test_function_args_usage(tests_path):
|
|
161
163
|
arg = farg.strip()
|
162
164
|
if fbody.find(arg) < 0:
|
163
165
|
found_error = True
|
164
|
-
msg += 'FUNCTION,ARGUMENT= {} {}\n'
|
166
|
+
msg += f'FUNCTION,ARGUMENT= {fname} {arg}\n'
|
165
167
|
if found_error:
|
166
168
|
raise ValueError(msg)
|
167
169
|
|
168
170
|
|
171
|
+
# pylint: disable=invalid-name,unused-argument
|
172
|
+
|
173
|
+
|
169
174
|
def test_DependentCare(skip_jit):
|
170
175
|
"""
|
171
176
|
Tests the DependentCare function
|
@@ -199,7 +204,7 @@ tuple7 = (0, 1000, STD_in, 44, 0, STD_Aged_in, 1000, 3, 1, 0, 0, 2,
|
|
199
204
|
tuple8 = (1, 200, STD_in, 44, 0, STD_Aged_in, 1000, 3, 0, 0, 0, 2,
|
200
205
|
True, 0, 100000, 1, Charity_max_in)
|
201
206
|
tuple9 = (1, 1000, STD_in, 44, 0, STD_Aged_in, 1000, 3, 0, 0, 0, 2,
|
202
|
-
True, 0,100000, 1, Charity_max_in)
|
207
|
+
True, 0, 100000, 1, Charity_max_in)
|
203
208
|
expected = [12000, 15800, 13800, 14400, 6000, 6000, 0, 1000, 1350]
|
204
209
|
|
205
210
|
|
@@ -224,22 +229,28 @@ def test_StdDed(test_tuple, expected_value, skip_jit):
|
|
224
229
|
assert np.allclose(avalue, expected_value), f"{avalue} != {expected_value}"
|
225
230
|
|
226
231
|
|
227
|
-
tuple1 = (120000, 10000, 15000, 100, 2000,
|
228
|
-
|
232
|
+
tuple1 = (120000, 10000, 15000, 100, 2000,
|
233
|
+
0.06, 0.06, 0.015, 0.015, 0, 99999999999,
|
234
|
+
400, 0, 0, 0, 0, 0, 0, None, None, None, None, None, None,
|
229
235
|
None, None, None, None, None)
|
230
|
-
tuple2 = (120000, 10000, 15000, 100, 2000,
|
236
|
+
tuple2 = (120000, 10000, 15000, 100, 2000,
|
237
|
+
0.06, 0.06, 0.015, 0.015, 0, 99999999999,
|
231
238
|
400, 2000, 0, 10000, 0, 0, 3000, None, None, None, None, None,
|
232
239
|
None, None, None, None, None, None)
|
233
|
-
tuple3 = (120000, 150000, 15000, 100, 2000,
|
240
|
+
tuple3 = (120000, 150000, 15000, 100, 2000,
|
241
|
+
0.06, 0.06, 0.015, 0.015, 0, 99999999999,
|
234
242
|
400, 2000, 0, 10000, 0, 0, 3000, None, None, None, None, None,
|
235
243
|
None, None, None, None, None, None)
|
236
|
-
tuple4 = (120000, 500000, 15000, 100, 2000,
|
244
|
+
tuple4 = (120000, 500000, 15000, 100, 2000,
|
245
|
+
0.06, 0.06, 0.015, 0.015, 0, 400000,
|
237
246
|
400, 2000, 0, 10000, 0, 0, 3000, None, None, None, None, None,
|
238
247
|
None, None, None, None, None, None)
|
239
|
-
tuple5 = (120000, 10000, 15000, 100, 2000,
|
248
|
+
tuple5 = (120000, 10000, 15000, 100, 2000,
|
249
|
+
0.06, 0.06, 0.015, 0.015, 0, 99999999999,
|
240
250
|
400, 300, 0, 0, 0, 0, 0, None, None, None, None, None,
|
241
251
|
None, None, None, None, None, None)
|
242
|
-
tuple6 = (120000, 10000, 15000, 100, 2000,
|
252
|
+
tuple6 = (120000, 10000, 15000, 100, 2000,
|
253
|
+
0.06, 0.06, 0.015, 0.015, 0, 99999999999,
|
243
254
|
400, 0, 0, 0, 0, -40000, 0, None, None, None, None, None,
|
244
255
|
None, None, None, None, None, None)
|
245
256
|
expected1 = (0, 4065, 4065, 0, 0, 3252, 25000, 10000, 15000, 10100,
|
@@ -254,6 +265,7 @@ expected5 = (300, 4065, 4065, 0, 0, 3285.3, 25300, 10279.1875, 15000,
|
|
254
265
|
10382, 17000)
|
255
266
|
expected6 = (-40000, 4065, 4065, 0, 0, 3252, 0, 0, 15000, 10100, 17000)
|
256
267
|
|
268
|
+
|
257
269
|
@pytest.mark.parametrize(
|
258
270
|
'test_input, expected_output', [
|
259
271
|
(tuple1, expected1),
|
@@ -272,7 +284,8 @@ def test_EI_PayrollTax(test_input, expected_output, skip_jit):
|
|
272
284
|
print('*INPUT:', test_input)
|
273
285
|
print('ACTUAL:', actual_output)
|
274
286
|
print('EXPECT:', expected_output)
|
275
|
-
assert
|
287
|
+
assert False, 'ERROR: ACTUAL != EXPECT'
|
288
|
+
|
276
289
|
|
277
290
|
def test_AfterTaxIncome(skip_jit):
|
278
291
|
'''
|
@@ -655,7 +668,7 @@ CTC_refundable = True
|
|
655
668
|
CTC_include17 = True
|
656
669
|
c07220 = 0 # actual value will be returned from function
|
657
670
|
odc = 0 # actual value will be returned from function
|
658
|
-
codtc_limited = 0
|
671
|
+
codtc_limited = 0 # actual value will be returned from function
|
659
672
|
tuple0 = (
|
660
673
|
age_head, age_spouse, nu18, n24, MARS, c00100, XTOT, num,
|
661
674
|
c05800, e07260, CR_ResidentialEnergy_hc,
|
@@ -673,8 +686,8 @@ expected0 = (0, 1000, 0)
|
|
673
686
|
|
674
687
|
|
675
688
|
@pytest.mark.parametrize(
|
676
|
-
'test_tuple,expected_value', [
|
677
|
-
|
689
|
+
'test_tuple,expected_value', [(tuple0, expected0)]
|
690
|
+
)
|
678
691
|
def test_ChildDepTaxCredit_2021(test_tuple, expected_value, skip_jit):
|
679
692
|
"""
|
680
693
|
Tests the ChildDepTaxCredit function
|
@@ -682,6 +695,7 @@ def test_ChildDepTaxCredit_2021(test_tuple, expected_value, skip_jit):
|
|
682
695
|
test_value = calcfunctions.ChildDepTaxCredit(*test_tuple)
|
683
696
|
assert np.allclose(test_value, expected_value)
|
684
697
|
|
698
|
+
|
685
699
|
# parameterization represents 2022 law
|
686
700
|
age_head = 45
|
687
701
|
age_spouse = 0
|
@@ -712,7 +726,7 @@ CTC_refundable = False
|
|
712
726
|
CTC_include17 = False
|
713
727
|
c07220 = 0 # actual value will be returned from function
|
714
728
|
odc = 0 # actual value will be returned from function
|
715
|
-
codtc_limited = 0
|
729
|
+
codtc_limited = 0 # actual value will be returned from function
|
716
730
|
tuple0 = (
|
717
731
|
age_head, age_spouse, nu18, n24, MARS, c00100, XTOT, num,
|
718
732
|
c05800, e07260, CR_ResidentialEnergy_hc,
|
@@ -739,6 +753,7 @@ def test_ChildDepTaxCredit_2022(test_tuple, expected_value, skip_jit):
|
|
739
753
|
test_value = calcfunctions.ChildDepTaxCredit(*test_tuple)
|
740
754
|
assert np.allclose(test_value, expected_value)
|
741
755
|
|
756
|
+
|
742
757
|
# parameterization represents 2021 law
|
743
758
|
CTC_new_c = 1000
|
744
759
|
CTC_new_rt = 0
|
@@ -762,7 +777,7 @@ c00100 = 1000
|
|
762
777
|
MARS = 4
|
763
778
|
ptax_oasdi = 0
|
764
779
|
c09200 = 0
|
765
|
-
ctc_new = 0
|
780
|
+
ctc_new = 0 # actual value will be returned from function
|
766
781
|
tuple0 = (
|
767
782
|
CTC_new_c, CTC_new_rt, CTC_new_c_under6_bonus,
|
768
783
|
CTC_new_ps, CTC_new_prt, CTC_new_for_all, CTC_include17,
|
@@ -771,7 +786,8 @@ tuple0 = (
|
|
771
786
|
n24, nu06, age_head, age_spouse, nu18, c00100, MARS, ptax_oasdi,
|
772
787
|
c09200, ctc_new)
|
773
788
|
# output tuple is : (ctc_new)
|
774
|
-
expected0 =
|
789
|
+
expected0 = 0
|
790
|
+
|
775
791
|
|
776
792
|
@pytest.mark.parametrize(
|
777
793
|
'test_tuple,expected_value', [
|
@@ -783,6 +799,7 @@ def test_CTCnew_2021(test_tuple, expected_value, skip_jit):
|
|
783
799
|
test_value = calcfunctions.CTC_new(*test_tuple)
|
784
800
|
assert np.allclose(test_value, expected_value)
|
785
801
|
|
802
|
+
|
786
803
|
# parameterization represents 2022 law
|
787
804
|
CTC_new_c = 0
|
788
805
|
CTC_new_rt = 0
|
@@ -806,7 +823,7 @@ c00100 = 1000
|
|
806
823
|
MARS = 4
|
807
824
|
ptax_oasdi = 0
|
808
825
|
c09200 = 0
|
809
|
-
ctc_new = 0
|
826
|
+
ctc_new = 0 # actual value will be returned from function
|
810
827
|
tuple0 = (
|
811
828
|
CTC_new_c, CTC_new_rt, CTC_new_c_under6_bonus,
|
812
829
|
CTC_new_ps, CTC_new_prt, CTC_new_for_all, CTC_include17,
|
@@ -815,11 +832,12 @@ tuple0 = (
|
|
815
832
|
n24, nu06, age_head, age_spouse, nu18, c00100, MARS, ptax_oasdi,
|
816
833
|
c09200, ctc_new)
|
817
834
|
# output tuple is : (ctc_new)
|
818
|
-
expected0 =
|
835
|
+
expected0 = 0
|
836
|
+
|
819
837
|
|
820
838
|
@pytest.mark.parametrize(
|
821
|
-
'test_tuple,expected_value', [
|
822
|
-
|
839
|
+
'test_tuple,expected_value', [(tuple0, expected0)]
|
840
|
+
)
|
823
841
|
def test_CTCnew_2022(test_tuple, expected_value, skip_jit):
|
824
842
|
"""
|
825
843
|
Tests the CTCnew function
|
@@ -828,7 +846,7 @@ def test_CTCnew_2022(test_tuple, expected_value, skip_jit):
|
|
828
846
|
assert np.allclose(test_value, expected_value)
|
829
847
|
|
830
848
|
|
831
|
-
|
849
|
+
# parameters for next test
|
832
850
|
ymod1 = 19330 + 10200
|
833
851
|
c02500 = 0
|
834
852
|
c02900 = 0
|
@@ -859,8 +877,8 @@ expected0 = (19330, 0, 0)
|
|
859
877
|
|
860
878
|
|
861
879
|
@pytest.mark.parametrize(
|
862
|
-
'test_tuple,expected_value', [
|
863
|
-
|
880
|
+
'test_tuple,expected_value', [(tuple0, expected0)]
|
881
|
+
)
|
864
882
|
def test_AGI(test_tuple, expected_value, skip_jit):
|
865
883
|
"""
|
866
884
|
Tests the TaxInc function
|
@@ -868,4 +886,3 @@ def test_AGI(test_tuple, expected_value, skip_jit):
|
|
868
886
|
test_value = calcfunctions.AGI(*test_tuple)
|
869
887
|
print('Returned from agi function: ', test_value)
|
870
888
|
assert np.allclose(test_value, expected_value)
|
871
|
-
|
taxcalc/tests/test_calculator.py
CHANGED
@@ -38,7 +38,7 @@ def test_make_calculator(cps_subsample):
|
|
38
38
|
with pytest.raises(ValueError):
|
39
39
|
Calculator(policy=pol, records=None)
|
40
40
|
with pytest.raises(ValueError):
|
41
|
-
Calculator(policy=pol, records=rec, consumption=
|
41
|
+
Calculator(policy=pol, records=rec, consumption=[])
|
42
42
|
|
43
43
|
|
44
44
|
def test_make_calculator_deepcopy(cps_subsample):
|
@@ -74,9 +74,9 @@ def test_make_calculator_with_policy_reform(cps_subsample):
|
|
74
74
|
assert calc.current_year == year
|
75
75
|
assert calc.policy_param('II_em') == 4000
|
76
76
|
assert np.allclose(calc.policy_param('_II_em'),
|
77
|
-
np.array([4000] *
|
77
|
+
np.array([4000] * pol.num_years))
|
78
78
|
exp_STD_Aged = [[1600, 1300, 1300,
|
79
|
-
1600, 1600]] *
|
79
|
+
1600, 1600]] * pol.num_years
|
80
80
|
assert np.allclose(calc.policy_param('_STD_Aged'),
|
81
81
|
np.array(exp_STD_Aged))
|
82
82
|
assert np.allclose(calc.policy_param('STD_Aged'),
|
@@ -100,10 +100,9 @@ def test_make_calculator_with_multiyear_reform(cps_subsample):
|
|
100
100
|
# create a Calculator object using this policy-reform
|
101
101
|
calc = Calculator(policy=pol, records=rec)
|
102
102
|
# check that Policy object embedded in Calculator object is correct
|
103
|
-
assert pol.num_years == Policy.DEFAULT_NUM_YEARS
|
104
103
|
assert calc.current_year == year
|
105
104
|
assert calc.policy_param('II_em') == 3950
|
106
|
-
exp_II_em = [3900, 3950, 5000] + [6000] * (
|
105
|
+
exp_II_em = [3900, 3950, 5000] + [6000] * (pol.num_years - 3)
|
107
106
|
assert np.allclose(calc.policy_param('_II_em'),
|
108
107
|
np.array(exp_II_em))
|
109
108
|
calc.increment_year()
|
@@ -523,7 +522,7 @@ def test_read_bad_json_assump_file():
|
|
523
522
|
with pytest.raises(ValueError):
|
524
523
|
Calculator.read_json_param_objects(None, 'unknown_file_name')
|
525
524
|
with pytest.raises(TypeError):
|
526
|
-
Calculator.read_json_param_objects(None,
|
525
|
+
Calculator.read_json_param_objects(None, [])
|
527
526
|
|
528
527
|
|
529
528
|
def test_json_doesnt_exist():
|
@@ -630,7 +629,7 @@ def test_reform_documentation():
|
|
630
629
|
dump = False # set to True to print documentation and force test failure
|
631
630
|
if dump:
|
632
631
|
print(doc)
|
633
|
-
assert
|
632
|
+
assert False, 'ERROR: reform_documentation above'
|
634
633
|
|
635
634
|
|
636
635
|
def test_distribution_tables(cps_subsample):
|
@@ -828,7 +827,7 @@ def test_itemded_component_amounts(year, cvname, hcname, puf_fullsample):
|
|
828
827
|
itmded1 = calc1.weighted_total('c04470') * 1e-9
|
829
828
|
itmded2 = calc2.weighted_total('c04470') * 1e-9
|
830
829
|
else:
|
831
|
-
raise ValueError('illegal year value = {}'
|
830
|
+
raise ValueError(f'illegal year value = {year}')
|
832
831
|
difference_in_total_itmded = itmded1 - itmded2
|
833
832
|
# calculate itemized component amount
|
834
833
|
component_amt = calc1.weighted_total(cvname) * 1e-9
|
@@ -842,8 +841,11 @@ def test_itemded_component_amounts(year, cvname, hcname, puf_fullsample):
|
|
842
841
|
else:
|
843
842
|
atol = 0.00001
|
844
843
|
if not np.allclose(component_amt, difference_in_total_itmded, atol=atol):
|
845
|
-
|
846
|
-
|
844
|
+
msg = (
|
845
|
+
f'\n{cvname}={component_amt:.3f} != '
|
846
|
+
f'{difference_in_total_itmded:.3f}='
|
847
|
+
'difference_in_total_itemized_deductions'
|
848
|
+
)
|
847
849
|
raise ValueError(msg)
|
848
850
|
|
849
851
|
|
@@ -929,6 +931,8 @@ def test_cg_top_rate():
|
|
929
931
|
"""
|
930
932
|
Test top CG bracket and rate.
|
931
933
|
"""
|
934
|
+
# pylint: disable=too-many-locals
|
935
|
+
|
932
936
|
cy = 2019
|
933
937
|
|
934
938
|
# set NIIT and STD to zero to isolate CG tax rates
|
taxcalc/tests/test_compare.py
CHANGED
@@ -8,22 +8,21 @@ Compares Tax-Calculator PUF and CPS results with historical information.
|
|
8
8
|
import os
|
9
9
|
import pytest
|
10
10
|
import numpy as np
|
11
|
-
|
12
|
-
from taxcalc import
|
13
|
-
from taxcalc import
|
11
|
+
from taxcalc.policy import Policy
|
12
|
+
from taxcalc.records import Records
|
13
|
+
from taxcalc.calculator import Calculator
|
14
|
+
from taxcalc.utils import add_income_table_row_variable, SOI_AGI_BINS
|
14
15
|
|
15
16
|
|
16
|
-
"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
to the nearest one-tenth of a million dollars.
|
26
|
-
"""
|
17
|
+
# 2015 IRS-SOI amounts by AGI category are from "Table 3.3 All Returns: Tax
|
18
|
+
# Liability, Tax Credits, and Tax Payments by Size of Adjusted Gross Income,
|
19
|
+
# Tax Year 2015" which is available as a spreadsheet at this URL:
|
20
|
+
# <https://www.irs.gov/statistics/soi-tax-stats-individual-
|
21
|
+
# statistical-tables-by-size-of-adjusted-gross-income>
|
22
|
+
# The IRS-SOI amounts are from 19 rows in the spreadsheet numbered from
|
23
|
+
# 11 (AGI under one dollar) through 29 (AGI $10M or more).
|
24
|
+
# Dollar IRS-SOI amounts are expressed in billions of dollars and rounded
|
25
|
+
# to the nearest one-tenth of a million dollars.
|
27
26
|
ITAX = {
|
28
27
|
'0:EITC': {
|
29
28
|
# Full earned income credit
|
@@ -204,11 +203,12 @@ def comparison(cname, calc, cmpdata, ofile):
|
|
204
203
|
# construct AGI table
|
205
204
|
vardf = add_income_table_row_variable(vardf, 'c00100', SOI_AGI_BINS)
|
206
205
|
gbydf = vardf.groupby('table_row', as_index=False)
|
207
|
-
# write AGI table with ALL row at bottom
|
208
|
-
ofile.write('TABLE for {
|
206
|
+
# write AGI table with ALL row at bottom of ofile
|
207
|
+
ofile.write(f'TABLE for {cname.split(":")[1]}\n')
|
209
208
|
results = '{:23s}\t{:8.3f}\t{:8.3f}\t{:+6.1f}\n'
|
210
|
-
colhead = '{:23s}\t{:>8s}\t{:>8s}\t{:>6s}\n'
|
211
|
-
ofile.write(colhead
|
209
|
+
colhead = f'{"AGIcategory":23s}\t{"T-C":>8s}\t{"SOI":>8s}\t{"%diff":>6s}\n'
|
210
|
+
ofile.write(colhead)
|
211
|
+
# pylint: disable=consider-using-f-string
|
212
212
|
txc_tot = 0.
|
213
213
|
soi_tot = 0.
|
214
214
|
idx = 0
|
@@ -221,12 +221,12 @@ def comparison(cname, calc, cmpdata, ofile):
|
|
221
221
|
pct_diff = 100. * ((txc / soi) - 1.)
|
222
222
|
else:
|
223
223
|
pct_diff = np.nan
|
224
|
-
glabel = '[{:.8g}, {:.8g})'
|
225
|
-
grp_interval.right)
|
224
|
+
glabel = f'[{grp_interval.left:.8g}, {grp_interval.right:.8g})'
|
226
225
|
ofile.write(results.format(glabel, txc, soi, pct_diff))
|
227
226
|
idx += 1
|
228
227
|
pct_diff = 100. * ((txc_tot / soi_tot) - 1.)
|
229
228
|
ofile.write(results.format('ALL', txc_tot, soi_tot, pct_diff))
|
229
|
+
# pylint: enable=consider-using-f-string
|
230
230
|
|
231
231
|
|
232
232
|
def nonsmall_diffs(linelist1, linelist2, small=0.0):
|
@@ -256,24 +256,20 @@ def nonsmall_diffs(linelist1, linelist2, small=0.0):
|
|
256
256
|
for line1, line2 in zip(linelist1, linelist2):
|
257
257
|
if line1 == line2:
|
258
258
|
continue
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
if
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
else:
|
274
|
-
return True
|
275
|
-
else:
|
276
|
-
return True
|
259
|
+
tokens1 = line1.replace(',', '').split()
|
260
|
+
tokens2 = line2.replace(',', '').split()
|
261
|
+
for tok1, tok2 in zip(tokens1, tokens2):
|
262
|
+
tok1_isfloat = isfloat(tok1)
|
263
|
+
tok2_isfloat = isfloat(tok2)
|
264
|
+
if tok1_isfloat and tok2_isfloat:
|
265
|
+
if abs(float(tok1) - float(tok2)) <= smallamt:
|
266
|
+
continue
|
267
|
+
return True
|
268
|
+
if not tok1_isfloat and not tok2_isfloat:
|
269
|
+
if tok1 == tok2:
|
270
|
+
continue
|
271
|
+
return True
|
272
|
+
return True
|
277
273
|
return False
|
278
274
|
|
279
275
|
|
@@ -281,9 +277,9 @@ def differences(afilename, efilename):
|
|
281
277
|
"""
|
282
278
|
Check for differences between results in afilename and efilename files.
|
283
279
|
"""
|
284
|
-
with open(afilename, 'r') as afile:
|
280
|
+
with open(afilename, 'r', encoding='utf-8') as afile:
|
285
281
|
actres = afile.read()
|
286
|
-
with open(efilename, 'r') as efile:
|
282
|
+
with open(efilename, 'r', encoding='utf-8') as efile:
|
287
283
|
expres = efile.read()
|
288
284
|
diffs = nonsmall_diffs(actres.splitlines(True),
|
289
285
|
expres.splitlines(True), 0.0)
|
@@ -292,12 +288,12 @@ def differences(afilename, efilename):
|
|
292
288
|
efname = os.path.basename(efilename)
|
293
289
|
msg = 'COMPARE RESULTS DIFFER\n'
|
294
290
|
msg += '-------------------------------------------------\n'
|
295
|
-
msg += '--- NEW RESULTS IN {} FILE
|
296
|
-
msg += '--- if new OK, copy {} to
|
297
|
-
msg += '--- {}
|
291
|
+
msg += f'--- NEW RESULTS IN {afname} FILE ---\n'
|
292
|
+
msg += f'--- if new OK, copy {afname} to ---\n'
|
293
|
+
msg += f'--- {efname} ---\n'
|
298
294
|
msg += '--- and rerun test. ---\n'
|
299
295
|
msg += '-------------------------------------------------\n'
|
300
|
-
raise ValueError(msg
|
296
|
+
raise ValueError(msg)
|
301
297
|
os.remove(afilename)
|
302
298
|
|
303
299
|
|
@@ -325,12 +321,10 @@ def test_itax_compare(tests_path, using_puf, puf_fullsample, cps_fullsample):
|
|
325
321
|
afilename = os.path.join(tests_path, 'cmpi_puf_actual.txt')
|
326
322
|
else:
|
327
323
|
afilename = os.path.join(tests_path, 'cmpi_cps_actual.txt')
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
# close actual output file
|
333
|
-
afile.close()
|
324
|
+
with open(afilename, 'w', encoding='utf-8') as afile:
|
325
|
+
# write compare results to afile
|
326
|
+
for cname in sorted(ITAX.keys()):
|
327
|
+
comparison(cname, calc, ITAX, afile)
|
334
328
|
# check for differences between actual and expect output files
|
335
329
|
efilename = afilename.replace('actual', 'expect')
|
336
330
|
differences(afilename, efilename)
|