taxcalc 4.4.0__py3-none-any.whl → 4.5.0__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.json +1 -1
- taxcalc/consumption.py +9 -4
- taxcalc/cps_weights.csv.gz +0 -0
- taxcalc/data.py +8 -8
- taxcalc/decorators.py +3 -3
- taxcalc/growdiff.json +1 -1
- taxcalc/growdiff.py +5 -0
- taxcalc/growfactors.csv +26 -25
- taxcalc/growfactors.py +1 -1
- taxcalc/parameters.py +85 -42
- taxcalc/policy.py +2 -2
- taxcalc/policy_current_law.json +87 -87
- taxcalc/puf_ratios.csv +15 -14
- taxcalc/puf_weights.csv.gz +0 -0
- taxcalc/records.py +1 -0
- taxcalc/records_variables.json +6 -0
- taxcalc/reforms/ext.json +21 -21
- taxcalc/taxcalcio.py +49 -44
- taxcalc/tests/cmpi_cps_expect.txt +6 -6
- taxcalc/tests/cmpi_puf_expect.txt +6 -6
- taxcalc/tests/conftest.py +43 -42
- taxcalc/tests/cpscsv_agg_expect.csv +22 -22
- taxcalc/tests/puf_var_wght_means_by_year.csv +70 -70
- taxcalc/tests/pufcsv_agg_expect.csv +22 -22
- taxcalc/tests/test_4package.py +9 -7
- taxcalc/tests/test_benefits.py +9 -8
- taxcalc/tests/test_calcfunctions.py +55 -38
- taxcalc/tests/test_calculator.py +11 -6
- taxcalc/tests/test_compare.py +45 -51
- taxcalc/tests/test_compatible_data.py +9 -7
- taxcalc/tests/test_consumption.py +38 -18
- taxcalc/tests/test_cpscsv.py +33 -31
- taxcalc/tests/test_data.py +31 -24
- taxcalc/tests/test_decorators.py +84 -32
- taxcalc/tests/test_growdiff.py +16 -13
- taxcalc/tests/test_growfactors.py +8 -8
- taxcalc/tests/test_parameters.py +55 -59
- taxcalc/tests/test_policy.py +14 -12
- 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 +35 -32
- 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.4.0.dist-info → taxcalc-4.5.0.dist-info}/METADATA +19 -5
- {taxcalc-4.4.0.dist-info → taxcalc-4.5.0.dist-info}/RECORD +57 -57
- {taxcalc-4.4.0.dist-info → taxcalc-4.5.0.dist-info}/WHEEL +1 -1
- {taxcalc-4.4.0.dist-info → taxcalc-4.5.0.dist-info}/LICENSE +0 -0
- {taxcalc-4.4.0.dist-info → taxcalc-4.5.0.dist-info}/entry_points.txt +0 -0
- {taxcalc-4.4.0.dist-info → taxcalc-4.5.0.dist-info}/top_level.txt +0 -0
@@ -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):
|
@@ -522,7 +522,7 @@ def test_read_bad_json_assump_file():
|
|
522
522
|
with pytest.raises(ValueError):
|
523
523
|
Calculator.read_json_param_objects(None, 'unknown_file_name')
|
524
524
|
with pytest.raises(TypeError):
|
525
|
-
Calculator.read_json_param_objects(None,
|
525
|
+
Calculator.read_json_param_objects(None, [])
|
526
526
|
|
527
527
|
|
528
528
|
def test_json_doesnt_exist():
|
@@ -629,7 +629,7 @@ def test_reform_documentation():
|
|
629
629
|
dump = False # set to True to print documentation and force test failure
|
630
630
|
if dump:
|
631
631
|
print(doc)
|
632
|
-
assert
|
632
|
+
assert False, 'ERROR: reform_documentation above'
|
633
633
|
|
634
634
|
|
635
635
|
def test_distribution_tables(cps_subsample):
|
@@ -827,7 +827,7 @@ def test_itemded_component_amounts(year, cvname, hcname, puf_fullsample):
|
|
827
827
|
itmded1 = calc1.weighted_total('c04470') * 1e-9
|
828
828
|
itmded2 = calc2.weighted_total('c04470') * 1e-9
|
829
829
|
else:
|
830
|
-
raise ValueError('illegal year value = {}'
|
830
|
+
raise ValueError(f'illegal year value = {year}')
|
831
831
|
difference_in_total_itmded = itmded1 - itmded2
|
832
832
|
# calculate itemized component amount
|
833
833
|
component_amt = calc1.weighted_total(cvname) * 1e-9
|
@@ -841,8 +841,11 @@ def test_itemded_component_amounts(year, cvname, hcname, puf_fullsample):
|
|
841
841
|
else:
|
842
842
|
atol = 0.00001
|
843
843
|
if not np.allclose(component_amt, difference_in_total_itmded, atol=atol):
|
844
|
-
|
845
|
-
|
844
|
+
msg = (
|
845
|
+
f'\n{cvname}={component_amt:.3f} != '
|
846
|
+
f'{difference_in_total_itmded:.3f}='
|
847
|
+
'difference_in_total_itemized_deductions'
|
848
|
+
)
|
846
849
|
raise ValueError(msg)
|
847
850
|
|
848
851
|
|
@@ -928,6 +931,8 @@ def test_cg_top_rate():
|
|
928
931
|
"""
|
929
932
|
Test top CG bracket and rate.
|
930
933
|
"""
|
934
|
+
# pylint: disable=too-many-locals
|
935
|
+
|
931
936
|
cy = 2019
|
932
937
|
|
933
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
|
@@ -203,12 +202,13 @@ def comparison(cname, calc, cmpdata, ofile):
|
|
203
202
|
vardf['cvar'] = cvar
|
204
203
|
# construct AGI table
|
205
204
|
vardf = add_income_table_row_variable(vardf, 'c00100', SOI_AGI_BINS)
|
206
|
-
gbydf = vardf.groupby('table_row', as_index=False)
|
207
|
-
# write AGI table with ALL row at bottom
|
208
|
-
ofile.write('TABLE for {
|
205
|
+
gbydf = vardf.groupby('table_row', as_index=False, observed=True)
|
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)
|
@@ -12,7 +12,9 @@ plug-in pytest-xdist is able to run all parametrized functions in parallel
|
|
12
12
|
import copy
|
13
13
|
import pytest
|
14
14
|
import numpy as np
|
15
|
-
from taxcalc import Policy
|
15
|
+
from taxcalc.policy import Policy
|
16
|
+
from taxcalc.records import Records
|
17
|
+
from taxcalc.calculator import Calculator
|
16
18
|
|
17
19
|
|
18
20
|
@pytest.fixture(scope='module', name='allparams')
|
@@ -49,7 +51,7 @@ def test_compatible_data_presence(allparams):
|
|
49
51
|
return True
|
50
52
|
|
51
53
|
# Main logic of test_compatible_data_presence function
|
52
|
-
problem_pnames =
|
54
|
+
problem_pnames = []
|
53
55
|
for pname in allparams:
|
54
56
|
if 'compatible_data' in allparams[pname]:
|
55
57
|
compatible_data = allparams[pname]['compatible_data']
|
@@ -61,7 +63,7 @@ def test_compatible_data_presence(allparams):
|
|
61
63
|
msg = '{} has no or invalid compatible_data field'
|
62
64
|
for pname in problem_pnames:
|
63
65
|
print(msg.format(pname))
|
64
|
-
assert 'list of problem_pnames
|
66
|
+
assert False, 'ERROR: list of problem_pnames is above'
|
65
67
|
|
66
68
|
|
67
69
|
XX_YEAR = 2019
|
@@ -142,7 +144,7 @@ BATCHES = int(np.floor(NPARAMS / BATCHSIZE)) + 1
|
|
142
144
|
|
143
145
|
|
144
146
|
@pytest.fixture(scope='module', name='allparams_batch',
|
145
|
-
params=
|
147
|
+
params=list(range(0, BATCHES)))
|
146
148
|
def fixture_allparams_batch(request, allparams, sorted_param_names):
|
147
149
|
"""
|
148
150
|
Fixture for grouping Tax-Calculator parameters
|
@@ -229,8 +231,8 @@ def test_compatible_data(cps_subsample, puf_subsample,
|
|
229
231
|
at least one of these reforms when using datasets marked compatible
|
230
232
|
and does not differ when using datasets marked as incompatible.
|
231
233
|
"""
|
232
|
-
# pylint: disable=too-many-arguments,too-many-
|
233
|
-
# pylint: disable=too-many-statements,too-many-branches
|
234
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
235
|
+
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
234
236
|
|
235
237
|
# Check NPARAMS value
|
236
238
|
assert NPARAMS == len(allparams)
|
@@ -335,4 +337,4 @@ def test_compatible_data(cps_subsample, puf_subsample,
|
|
335
337
|
# test failure if any errors
|
336
338
|
if errors:
|
337
339
|
print(errors)
|
338
|
-
assert 'compatible_data
|
340
|
+
assert False, 'ERROR: compatible_data is invalid; see errors above'
|