taxcalc 4.2.1__py3-none-any.whl → 4.3.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/assumptions/ASSUMPTIONS.md +53 -0
- taxcalc/assumptions/README.md +17 -0
- taxcalc/assumptions/economic_assumptions_template.json +77 -0
- taxcalc/calcfunctions.py +7 -4
- taxcalc/data.py +10 -5
- taxcalc/policy.py +1 -1
- taxcalc/policy_current_law.json +4649 -288
- taxcalc/records.py +20 -15
- taxcalc/reforms/2017_law.json +125 -0
- taxcalc/reforms/2017_law.out.csv +10 -0
- taxcalc/reforms/ARPA.json +78 -0
- taxcalc/reforms/ARPA.out.csv +10 -0
- taxcalc/reforms/BrownKhanna.json +23 -0
- taxcalc/reforms/BrownKhanna.out.csv +10 -0
- taxcalc/reforms/CARES.json +40 -0
- taxcalc/reforms/CARES.out.csv +10 -0
- taxcalc/reforms/ConsolidatedAppropriationsAct2021.json +15 -0
- taxcalc/reforms/ConsolidatedAppropriationsAct2021.out.csv +10 -0
- taxcalc/reforms/Larson2019.json +36 -0
- taxcalc/reforms/Larson2019.out.csv +10 -0
- taxcalc/reforms/README.md +22 -0
- taxcalc/reforms/REFORMS.md +92 -0
- taxcalc/reforms/Renacci.json +61 -0
- taxcalc/reforms/Renacci.out.csv +10 -0
- taxcalc/reforms/SandersDeFazio.json +15 -0
- taxcalc/reforms/SandersDeFazio.out.csv +10 -0
- taxcalc/reforms/TCJA.json +160 -0
- taxcalc/reforms/TCJA.md +48 -0
- taxcalc/reforms/TCJA.out.csv +10 -0
- taxcalc/reforms/Trump2016.json +71 -0
- taxcalc/reforms/Trump2016.out.csv +10 -0
- taxcalc/reforms/Trump2017.json +51 -0
- taxcalc/reforms/Trump2017.out.csv +10 -0
- taxcalc/reforms/archive/Clinton2016.json +56 -0
- taxcalc/reforms/archive/RyanBrady.json +104 -0
- taxcalc/reforms/archive/TCJA_House.json +144 -0
- taxcalc/reforms/archive/TCJA_House_Amended.json +152 -0
- taxcalc/reforms/archive/TCJA_Reconciliation.json +187 -0
- taxcalc/reforms/archive/TCJA_Senate.json +116 -0
- taxcalc/reforms/archive/TCJA_Senate_111417.json +169 -0
- taxcalc/reforms/archive/TCJA_Senate_120117.json +174 -0
- taxcalc/reforms/cases.csv +10 -0
- taxcalc/reforms/clp.out.csv +10 -0
- taxcalc/reforms/ext.json +59 -0
- taxcalc/reforms/growfactors_ext.csv +65 -0
- taxcalc/reforms/ptaxes0.json +37 -0
- taxcalc/reforms/ptaxes0.out.csv +10 -0
- taxcalc/reforms/ptaxes1.json +21 -0
- taxcalc/reforms/ptaxes1.out.csv +10 -0
- taxcalc/reforms/ptaxes2.json +18 -0
- taxcalc/reforms/ptaxes2.out.csv +10 -0
- taxcalc/reforms/ptaxes3.json +28 -0
- taxcalc/reforms/ptaxes3.out.csv +10 -0
- taxcalc/taxcalcio.py +44 -22
- taxcalc/tests/benefits_expect.csv +169 -0
- taxcalc/tests/cmpi_cps_expect.txt +132 -0
- taxcalc/tests/cmpi_puf_expect.txt +132 -0
- taxcalc/tests/conftest.py +143 -0
- taxcalc/tests/cpscsv_agg_expect.csv +26 -0
- taxcalc/tests/puf_var_correl_coeffs_2016.csv +80 -0
- taxcalc/tests/puf_var_wght_means_by_year.csv +80 -0
- taxcalc/tests/pufcsv_agg_expect.csv +26 -0
- taxcalc/tests/pufcsv_mtr_expect.txt +63 -0
- taxcalc/tests/reforms.json +649 -0
- taxcalc/tests/reforms_expect.csv +65 -0
- taxcalc/tests/test_4package.py +67 -0
- taxcalc/tests/test_benefits.py +86 -0
- taxcalc/tests/test_calcfunctions.py +871 -0
- taxcalc/tests/test_calculator.py +1021 -0
- taxcalc/tests/test_compare.py +336 -0
- taxcalc/tests/test_compatible_data.py +338 -0
- taxcalc/tests/test_consumption.py +144 -0
- taxcalc/tests/test_cpscsv.py +163 -0
- taxcalc/tests/test_data.py +133 -0
- taxcalc/tests/test_decorators.py +332 -0
- taxcalc/tests/test_growdiff.py +102 -0
- taxcalc/tests/test_growfactors.py +94 -0
- taxcalc/tests/test_parameters.py +617 -0
- taxcalc/tests/test_policy.py +1557 -0
- taxcalc/tests/test_puf_var_stats.py +194 -0
- taxcalc/tests/test_pufcsv.py +385 -0
- taxcalc/tests/test_records.py +234 -0
- taxcalc/tests/test_reforms.py +386 -0
- taxcalc/tests/test_responses.py +41 -0
- taxcalc/tests/test_taxcalcio.py +755 -0
- taxcalc/tests/test_utils.py +792 -0
- taxcalc/validation/CSV_INPUT_VARS.md +29 -0
- taxcalc/validation/CSV_OUTPUT_VARS.md +63 -0
- taxcalc/validation/README.md +68 -0
- taxcalc/validation/taxsim35/Differences_Explained.md +54 -0
- taxcalc/validation/taxsim35/README.md +139 -0
- taxcalc/validation/taxsim35/expected_differences/a17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a20-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a21-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b20-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b21-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/input_setup.py +67 -0
- taxcalc/validation/taxsim35/main_comparison.py +183 -0
- taxcalc/validation/taxsim35/prepare_taxcalc_input.py +161 -0
- taxcalc/validation/taxsim35/process_taxcalc_output.py +140 -0
- taxcalc/validation/taxsim35/taxsim_emulation.json +49 -0
- taxcalc/validation/taxsim35/taxsim_input.py +321 -0
- taxcalc/validation/taxsim35/tc_sims.py +98 -0
- taxcalc/validation/taxsim35/tests_35.py +80 -0
- taxcalc/validation/tests_35.sh +13 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/METADATA +3 -4
- taxcalc-4.3.0.dist-info/RECORD +139 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/WHEEL +1 -1
- taxcalc/tmd_growfactors.csv +0 -55
- taxcalc/tmd_weights.csv.gz +0 -0
- taxcalc-4.2.1.dist-info/RECORD +0 -34
- {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/LICENSE +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/entry_points.txt +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
# CODING-STYLE CHECKS:
|
2
|
+
# pycodestyle test_records.py
|
3
|
+
|
4
|
+
import os
|
5
|
+
import json
|
6
|
+
import numpy as np
|
7
|
+
from numpy.testing import assert_array_equal
|
8
|
+
import pandas as pd
|
9
|
+
import pytest
|
10
|
+
from io import StringIO
|
11
|
+
from taxcalc import GrowFactors, Policy, Records, Calculator
|
12
|
+
|
13
|
+
|
14
|
+
def test_incorrect_Records_instantiation(cps_subsample):
|
15
|
+
with pytest.raises(ValueError):
|
16
|
+
recs = Records(data=list())
|
17
|
+
with pytest.raises(ValueError):
|
18
|
+
recs = Records(data=cps_subsample, gfactors=list())
|
19
|
+
with pytest.raises(ValueError):
|
20
|
+
recs = Records(data=cps_subsample, gfactors=None, weights=list())
|
21
|
+
with pytest.raises(ValueError):
|
22
|
+
recs = Records(data=cps_subsample, gfactors=None, weights=None,
|
23
|
+
start_year=list())
|
24
|
+
with pytest.raises(ValueError):
|
25
|
+
recs = Records(data=cps_subsample, gfactors=None, weights=None,
|
26
|
+
adjust_ratios=list())
|
27
|
+
|
28
|
+
|
29
|
+
def test_correct_Records_instantiation(cps_subsample):
|
30
|
+
rec1 = Records.cps_constructor(data=cps_subsample, gfactors=None)
|
31
|
+
assert rec1
|
32
|
+
assert np.all(rec1.MARS != 0)
|
33
|
+
assert rec1.current_year == rec1.data_year
|
34
|
+
sum_e00200_in_cps_year = rec1.e00200.sum()
|
35
|
+
rec1.increment_year()
|
36
|
+
sum_e00200_in_cps_year_plus_one = rec1.e00200.sum()
|
37
|
+
assert sum_e00200_in_cps_year_plus_one == sum_e00200_in_cps_year
|
38
|
+
wghts_path = os.path.join(Records.CODE_PATH, Records.CPS_WEIGHTS_FILENAME)
|
39
|
+
wghts_df = pd.read_csv(wghts_path)
|
40
|
+
ratios_path = os.path.join(Records.CODE_PATH, Records.PUF_RATIOS_FILENAME)
|
41
|
+
ratios_df = pd.read_csv(ratios_path, index_col=0).transpose()
|
42
|
+
rec2 = Records(data=cps_subsample,
|
43
|
+
start_year=Records.CPSCSV_YEAR,
|
44
|
+
gfactors=GrowFactors(),
|
45
|
+
weights=wghts_df,
|
46
|
+
adjust_ratios=ratios_df,
|
47
|
+
exact_calculations=False)
|
48
|
+
assert rec2
|
49
|
+
assert np.all(rec2.MARS != 0)
|
50
|
+
assert rec2.current_year == rec2.data_year
|
51
|
+
|
52
|
+
|
53
|
+
def test_read_cps_data(cps_fullsample):
|
54
|
+
data = Records.read_cps_data()
|
55
|
+
assert data.equals(cps_fullsample)
|
56
|
+
|
57
|
+
|
58
|
+
@pytest.mark.parametrize("csv", [
|
59
|
+
(
|
60
|
+
u'RECID,MARS,e00200,e00200p,e00200s\n'
|
61
|
+
u'1, 2, 200000, 200000, 0.03\n'
|
62
|
+
),
|
63
|
+
(
|
64
|
+
u'RECID,MARS,e00900,e00900p,e00900s\n'
|
65
|
+
u'1, 2, 200000, 200000, 0.03\n'
|
66
|
+
),
|
67
|
+
(
|
68
|
+
u'RECID,MARS,e02100,e02100p,e02100s\n'
|
69
|
+
u'1, 2, 200000, 200000, 0.03\n'
|
70
|
+
),
|
71
|
+
(
|
72
|
+
u'RECID,MARS,e00200,e00200p,e00200s\n'
|
73
|
+
u'1, 4, 200000, 100000, 100000\n'
|
74
|
+
),
|
75
|
+
(
|
76
|
+
u'RECID,MARS,e00900,e00900p,e00900s\n'
|
77
|
+
u'1, 4, 200000, 100000, 100000\n'
|
78
|
+
),
|
79
|
+
(
|
80
|
+
u'RECID,MARS,e02100,e02100p,e02100s\n'
|
81
|
+
u'1, 4, 200000, 100000, 100000\n'
|
82
|
+
),
|
83
|
+
(
|
84
|
+
u'RECID,MARS,k1bx14s\n'
|
85
|
+
u'1, 4, 0.03\n'
|
86
|
+
),
|
87
|
+
(
|
88
|
+
u'RxCID,MARS\n'
|
89
|
+
u'1, 2\n'
|
90
|
+
),
|
91
|
+
(
|
92
|
+
u'RECID,e00300\n'
|
93
|
+
u'1, 456789\n'
|
94
|
+
),
|
95
|
+
(
|
96
|
+
u'RECID,MARS\n'
|
97
|
+
u'1, 6\n'
|
98
|
+
),
|
99
|
+
(
|
100
|
+
u'RECID,MARS,EIC\n'
|
101
|
+
u'1, 5, 4\n'
|
102
|
+
),
|
103
|
+
(
|
104
|
+
u'RECID,MARS,e00600,e00650\n'
|
105
|
+
u'1, 1, 8, 9\n'
|
106
|
+
),
|
107
|
+
(
|
108
|
+
u'RECID,MARS,e01500,e01700\n'
|
109
|
+
u'1, 1, 6, 7\n'
|
110
|
+
),
|
111
|
+
(
|
112
|
+
u'RECID,MARS,PT_SSTB_income\n'
|
113
|
+
u'1, 1, 2\n'
|
114
|
+
)
|
115
|
+
])
|
116
|
+
def test_read_data(csv):
|
117
|
+
df = pd.read_csv(StringIO(csv))
|
118
|
+
with pytest.raises(ValueError):
|
119
|
+
Records(data=df)
|
120
|
+
|
121
|
+
|
122
|
+
def test_for_duplicate_names():
|
123
|
+
records_varinfo = Records(data=None)
|
124
|
+
varnames = set()
|
125
|
+
for varname in records_varinfo.USABLE_READ_VARS:
|
126
|
+
assert varname not in varnames
|
127
|
+
varnames.add(varname)
|
128
|
+
assert varname not in records_varinfo.CALCULATED_VARS
|
129
|
+
varnames = set()
|
130
|
+
for varname in records_varinfo.CALCULATED_VARS:
|
131
|
+
assert varname not in varnames
|
132
|
+
varnames.add(varname)
|
133
|
+
assert varname not in records_varinfo.USABLE_READ_VARS
|
134
|
+
varnames = set()
|
135
|
+
for varname in records_varinfo.INTEGER_READ_VARS:
|
136
|
+
assert varname not in varnames
|
137
|
+
varnames.add(varname)
|
138
|
+
assert varname in records_varinfo.USABLE_READ_VARS
|
139
|
+
|
140
|
+
|
141
|
+
def test_records_variables_content(tests_path):
|
142
|
+
"""
|
143
|
+
Check completeness and consistency of records_variables.json content.
|
144
|
+
"""
|
145
|
+
# specify test information
|
146
|
+
reqkeys = ['type', 'desc', 'form']
|
147
|
+
first_year = Policy.JSON_START_YEAR
|
148
|
+
last_form_year = 2017
|
149
|
+
# read JSON variable file into a dictionary
|
150
|
+
path = os.path.join(tests_path, '..', 'records_variables.json')
|
151
|
+
vfile = open(path, 'r')
|
152
|
+
allvars = json.load(vfile)
|
153
|
+
vfile.close()
|
154
|
+
assert isinstance(allvars, dict)
|
155
|
+
# check elements in each variable dictionary
|
156
|
+
for iotype in ['read', 'calc']:
|
157
|
+
for vname in allvars[iotype]:
|
158
|
+
variable = allvars[iotype][vname]
|
159
|
+
assert isinstance(variable, dict)
|
160
|
+
# check that variable contains required keys
|
161
|
+
for key in reqkeys:
|
162
|
+
assert key in variable
|
163
|
+
# check that required is true if it is present
|
164
|
+
if 'required' in variable:
|
165
|
+
assert variable['required'] is True
|
166
|
+
# check that forminfo is dictionary with sensible year ranges
|
167
|
+
forminfo = variable['form']
|
168
|
+
assert isinstance(forminfo, dict)
|
169
|
+
yranges = sorted(forminfo.keys())
|
170
|
+
num_yranges = len(yranges)
|
171
|
+
prior_eyr = first_year - 1
|
172
|
+
yrange_num = 0
|
173
|
+
for yrange in yranges:
|
174
|
+
yrange_num += 1
|
175
|
+
yrlist = yrange.split('-')
|
176
|
+
fyr = int(yrlist[0])
|
177
|
+
if yrlist[1] == '20??':
|
178
|
+
indefinite_yrange = True
|
179
|
+
assert yrange_num == num_yranges
|
180
|
+
else:
|
181
|
+
indefinite_yrange = False
|
182
|
+
eyr = int(yrlist[1])
|
183
|
+
if fyr != (prior_eyr + 1):
|
184
|
+
msg1 = '{} fyr {}'.format(vname, fyr)
|
185
|
+
msg2 = '!= prior_eyr_1 {}'.format(prior_eyr + 1)
|
186
|
+
assert msg1 == msg2
|
187
|
+
if eyr > last_form_year:
|
188
|
+
msg1 = '{} eyr {}'.format(vname, eyr)
|
189
|
+
msg2 = '> last_form_year {}'.format(last_form_year)
|
190
|
+
assert msg1 == msg2
|
191
|
+
prior_eyr = eyr
|
192
|
+
if not indefinite_yrange and len(yranges) > 0:
|
193
|
+
prior_ey_ok = (prior_eyr == last_form_year or
|
194
|
+
prior_eyr == last_form_year - 1)
|
195
|
+
if not prior_ey_ok:
|
196
|
+
msg1 = '{} prior_eyr {}'.format(vname, prior_eyr)
|
197
|
+
msg2 = '!= last_form_year {}'.format(last_form_year)
|
198
|
+
assert msg1 == msg2
|
199
|
+
|
200
|
+
|
201
|
+
def test_csv_input_vars_md_contents(tests_path):
|
202
|
+
"""
|
203
|
+
Check CSV_INPUT_VARS.md contents against Records.USABLE_READ_VARS
|
204
|
+
"""
|
205
|
+
# read variable names in CSV_INPUT_VARS.md file (checking for duplicates)
|
206
|
+
civ_path = os.path.join(tests_path, '..', 'validation',
|
207
|
+
'CSV_INPUT_VARS.md')
|
208
|
+
civ_set = set()
|
209
|
+
with open(civ_path, 'r') as civfile:
|
210
|
+
msg = 'DUPLICATE VARIABLE(S) IN CSV_INPUT_VARS.MD FILE:\n'
|
211
|
+
found_duplicates = False
|
212
|
+
for line in civfile:
|
213
|
+
str_list = line.split('|', 2)
|
214
|
+
if len(str_list) != 3:
|
215
|
+
continue # because line is not part of the markdown table
|
216
|
+
assert str_list[0] == '' # because line starts with | character
|
217
|
+
var = (str_list[1]).strip() # remove surrounding whitespace
|
218
|
+
if var == 'Var-name' or var[0] == ':':
|
219
|
+
continue # skip two lines that are the table head
|
220
|
+
if var in civ_set:
|
221
|
+
found_duplicates = True
|
222
|
+
msg += 'VARIABLE= {}\n'.format(var)
|
223
|
+
else:
|
224
|
+
civ_set.add(var)
|
225
|
+
if found_duplicates:
|
226
|
+
raise ValueError(msg)
|
227
|
+
# check that civ_set is a subset of Records.USABLE_READ_VARS set
|
228
|
+
records_varinfo = Records(data=None)
|
229
|
+
if not civ_set.issubset(records_varinfo.USABLE_READ_VARS):
|
230
|
+
valid_less_civ = records_varinfo.USABLE_READ_VARS - civ_set
|
231
|
+
msg = 'VARIABLE(S) IN USABLE_READ_VARS BUT NOT CSV_INPUT_VARS.MD:\n'
|
232
|
+
for var in valid_less_civ:
|
233
|
+
msg += 'VARIABLE= {}\n'.format(var)
|
234
|
+
raise ValueError(msg)
|
@@ -0,0 +1,386 @@
|
|
1
|
+
"""
|
2
|
+
Test example JSON policy reform files in taxcalc/reforms directory
|
3
|
+
"""
|
4
|
+
# CODING-STYLE CHECKS:
|
5
|
+
# pycodestyle test_reforms.py
|
6
|
+
# pylint --disable=locally-disabled test_reforms.py
|
7
|
+
|
8
|
+
import os
|
9
|
+
import glob
|
10
|
+
import json
|
11
|
+
import pytest
|
12
|
+
import numpy as np
|
13
|
+
import pandas as pd
|
14
|
+
# pylint: disable=import-error
|
15
|
+
from taxcalc import Calculator, Policy, Records
|
16
|
+
|
17
|
+
|
18
|
+
def test_2017_law_reform(tests_path):
|
19
|
+
"""
|
20
|
+
Check that policy parameter values in a future year under current-law
|
21
|
+
policy and under the reform specified in the 2017_law.json file are
|
22
|
+
sensible.
|
23
|
+
"""
|
24
|
+
# create pre metadata dictionary for 2017_law.json reform in fyear
|
25
|
+
pol = Policy()
|
26
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
|
27
|
+
with open(reform_file, 'r') as rfile:
|
28
|
+
rtext = rfile.read()
|
29
|
+
pol.implement_reform(Policy.read_json_reform(rtext))
|
30
|
+
assert not pol.parameter_warnings
|
31
|
+
pol.set_year(2018)
|
32
|
+
pre_mdata = dict(pol.items())
|
33
|
+
# check some policy parameter values against expected values under 2017 law
|
34
|
+
pre_expect = {
|
35
|
+
# relation '<' implies asserting that actual < expect
|
36
|
+
# relation '>' implies asserting that actual > expect
|
37
|
+
# ... parameters not affected by TCJA and that are not indexed
|
38
|
+
'AMEDT_ec': {'relation': '=', 'value': 200000},
|
39
|
+
'SS_thd85': {'relation': '=', 'value': 34000},
|
40
|
+
# ... parameters not affected by TCJA and that are indexed
|
41
|
+
'STD_Dep': {'relation': '>', 'value': 1050},
|
42
|
+
'CG_brk2': {'relation': '>', 'value': 425400},
|
43
|
+
'AMT_CG_brk1': {'relation': '>', 'value': 38600},
|
44
|
+
'AMT_brk1': {'relation': '>', 'value': 191100},
|
45
|
+
'EITC_c': {'relation': '>', 'value': 519},
|
46
|
+
'EITC_ps': {'relation': '>', 'value': 8490},
|
47
|
+
'EITC_ps_MarriedJ': {'relation': '>', 'value': 5680},
|
48
|
+
'EITC_InvestIncome_c': {'relation': '>', 'value': 3500},
|
49
|
+
# ... parameters affected by TCJA and that are not indexed
|
50
|
+
'ID_Charity_crt_cash': {'relation': '=', 'value': 0.5},
|
51
|
+
'II_rt3': {'relation': '=', 'value': 0.25},
|
52
|
+
# ... parameters affected by TCJA and that are indexed
|
53
|
+
'II_brk3': {'relation': '>', 'value': 91900},
|
54
|
+
'STD': {'relation': '<', 'value': 7000},
|
55
|
+
'II_em': {'relation': '>', 'value': 4050},
|
56
|
+
'AMT_em_pe': {'relation': '<', 'value': 260000}
|
57
|
+
}
|
58
|
+
assert isinstance(pre_expect, dict)
|
59
|
+
assert set(pre_expect.keys()).issubset(set(pre_mdata.keys()))
|
60
|
+
for name in pre_expect:
|
61
|
+
aval = pre_mdata[name]
|
62
|
+
if aval.ndim == 2:
|
63
|
+
act = aval[0][0] # comparing only first item in a vector parameter
|
64
|
+
else:
|
65
|
+
act = aval[0]
|
66
|
+
exp = pre_expect[name]['value']
|
67
|
+
if pre_expect[name]['relation'] == '<':
|
68
|
+
assert act < exp, '{} a={} !< e={}'.format(name, act, exp)
|
69
|
+
elif pre_expect[name]['relation'] == '>':
|
70
|
+
assert act > exp, '{} a={} !> e={}'.format(name, act, exp)
|
71
|
+
elif pre_expect[name]['relation'] == '=':
|
72
|
+
assert act == exp, '{} a={} != e={}'.format(name, act, exp)
|
73
|
+
|
74
|
+
|
75
|
+
@pytest.mark.rtr
|
76
|
+
@pytest.mark.parametrize('fyear', [2019, 2020, 2021, 2022, 2023])
|
77
|
+
def test_round_trip_reforms(fyear, tests_path):
|
78
|
+
"""
|
79
|
+
Check that current-law policy has the same policy parameter values in
|
80
|
+
a future year as does a compound reform that first implements the
|
81
|
+
2017 tax law as specified in the 2017_law.json file and then implements
|
82
|
+
reforms that represents new tax legislation since 2017.
|
83
|
+
This test checks that the future-year parameter values for
|
84
|
+
current-law policy (which incorporates recent legislation such as
|
85
|
+
the TCJA, CARES Act, and ARPA) are the same as future-year
|
86
|
+
parameter values for the compound round-trip reform.
|
87
|
+
Doing this check ensures that the 2017_law.json
|
88
|
+
and subsequent reform files that represent recent legislation are
|
89
|
+
specified in a consistent manner.
|
90
|
+
"""
|
91
|
+
# pylint: disable=too-many-locals
|
92
|
+
# create clp metadata dictionary for current-law policy in fyear
|
93
|
+
clp_pol = Policy()
|
94
|
+
clp_pol.set_year(fyear)
|
95
|
+
clp_mdata = dict(clp_pol.items())
|
96
|
+
# create rtr metadata dictionary for round-trip reform in fyear
|
97
|
+
rtr_pol = Policy()
|
98
|
+
# Revert to 2017 law
|
99
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
|
100
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
101
|
+
rtext = rfile.read()
|
102
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
103
|
+
assert not rtr_pol.parameter_warnings
|
104
|
+
assert not rtr_pol.errors
|
105
|
+
# Layer on TCJA
|
106
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json')
|
107
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
108
|
+
rtext = rfile.read()
|
109
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
110
|
+
assert not rtr_pol.parameter_warnings
|
111
|
+
assert not rtr_pol.errors
|
112
|
+
# Layer on the CARES Act
|
113
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', 'CARES.json')
|
114
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
115
|
+
rtext = rfile.read()
|
116
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
117
|
+
# Layer on the Consolidated Appropriations Act of 2021
|
118
|
+
reform_file = os.path.join(
|
119
|
+
tests_path, '..', 'reforms', 'ConsolidatedAppropriationsAct2021.json'
|
120
|
+
)
|
121
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
122
|
+
rtext = rfile.read()
|
123
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
124
|
+
assert not rtr_pol.parameter_warnings
|
125
|
+
assert not rtr_pol.errors
|
126
|
+
# Layer on ARPA
|
127
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', 'ARPA.json')
|
128
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
129
|
+
rtext = rfile.read()
|
130
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
131
|
+
assert not rtr_pol.parameter_warnings
|
132
|
+
assert not rtr_pol.errors
|
133
|
+
# Layer on rounding from IRS through Policy.LAST_KNOWN_YEAR
|
134
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', 'rounding.json')
|
135
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
136
|
+
rtext = rfile.read()
|
137
|
+
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
138
|
+
assert not rtr_pol.parameter_warnings
|
139
|
+
assert not rtr_pol.errors
|
140
|
+
rtr_pol.set_year(fyear)
|
141
|
+
rtr_mdata = dict(rtr_pol.items())
|
142
|
+
# compare fyear policy parameter values
|
143
|
+
assert clp_mdata.keys() == rtr_mdata.keys()
|
144
|
+
fail_dump = False
|
145
|
+
if fail_dump:
|
146
|
+
rtr_fails = open('fails_rtr', 'w', encoding='utf-8')
|
147
|
+
clp_fails = open('fails_clp', 'w', encoding='utf-8')
|
148
|
+
fail_params = []
|
149
|
+
msg = '\nRound-trip-reform and current-law-policy param values differ for:'
|
150
|
+
for pname in clp_mdata.keys():
|
151
|
+
rtr_val = rtr_mdata[pname]
|
152
|
+
clp_val = clp_mdata[pname]
|
153
|
+
if not np.allclose(rtr_val, clp_val):
|
154
|
+
fail_params.append(pname)
|
155
|
+
msg += '\n {} in {} : rtr={} clp={}'.format(
|
156
|
+
pname, fyear, rtr_val, clp_val
|
157
|
+
)
|
158
|
+
if fail_dump:
|
159
|
+
rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val))
|
160
|
+
clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val))
|
161
|
+
if fail_dump:
|
162
|
+
rtr_fails.close()
|
163
|
+
clp_fails.close()
|
164
|
+
if fail_params:
|
165
|
+
raise ValueError(msg)
|
166
|
+
|
167
|
+
|
168
|
+
@pytest.mark.reforms
|
169
|
+
def test_reform_json_and_output(tests_path):
|
170
|
+
"""
|
171
|
+
Check that each JSON reform file can be converted into a reform dictionary
|
172
|
+
that can then be passed to the Policy class implement_reform method that
|
173
|
+
generates no parameter_errors.
|
174
|
+
Then use each reform to generate static tax results for small set of
|
175
|
+
filing units in a single tax_year and compare those results with
|
176
|
+
expected results from a CSV-formatted file.
|
177
|
+
"""
|
178
|
+
# pylint: disable=too-many-statements,too-many-locals
|
179
|
+
|
180
|
+
# embedded function used only in test_reform_json_and_output
|
181
|
+
def write_res_file(calc, resfilename):
|
182
|
+
"""
|
183
|
+
Write calc output to CSV-formatted file with resfilename.
|
184
|
+
"""
|
185
|
+
varlist = [
|
186
|
+
'RECID', 'c00100', 'standard', 'c04800', 'iitax', 'payrolltax'
|
187
|
+
]
|
188
|
+
# varnames AGI STD TaxInc ITAX PTAX
|
189
|
+
stats = calc.dataframe(varlist)
|
190
|
+
stats['RECID'] = stats['RECID'].astype(int)
|
191
|
+
with open(resfilename, 'w') as resfile:
|
192
|
+
stats.to_csv(resfile, index=False, float_format='%.2f')
|
193
|
+
|
194
|
+
# embedded function used only in test_reform_json_and_output
|
195
|
+
def res_and_out_are_same(base):
|
196
|
+
"""
|
197
|
+
Return True if base.res.csv and base.out.csv file contents are same;
|
198
|
+
return False if base.res.csv and base.out.csv file contents differ.
|
199
|
+
"""
|
200
|
+
resdf = pd.read_csv(base + '.res.csv')
|
201
|
+
outdf = pd.read_csv(base + '.out.csv')
|
202
|
+
diffs = False
|
203
|
+
for col in resdf:
|
204
|
+
if col in outdf:
|
205
|
+
if not np.allclose(resdf[col], outdf[col]):
|
206
|
+
diffs = True
|
207
|
+
else:
|
208
|
+
diffs = True
|
209
|
+
return not diffs
|
210
|
+
|
211
|
+
# specify Records object containing cases data
|
212
|
+
tax_year = 2020
|
213
|
+
cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv')
|
214
|
+
cases = Records(data=cases_path,
|
215
|
+
start_year=tax_year, # set raw input data year
|
216
|
+
gfactors=None, # keeps raw data unchanged
|
217
|
+
weights=None,
|
218
|
+
adjust_ratios=None)
|
219
|
+
# specify list of reform failures
|
220
|
+
failures = list()
|
221
|
+
# specify current-law-policy Calculator object
|
222
|
+
calc = Calculator(policy=Policy(), records=cases, verbose=False)
|
223
|
+
calc.advance_to_year(tax_year)
|
224
|
+
calc.calc_all()
|
225
|
+
res_path = cases_path.replace('cases.csv', 'clp.res.csv')
|
226
|
+
write_res_file(calc, res_path)
|
227
|
+
if res_and_out_are_same(res_path.replace('.res.csv', '')):
|
228
|
+
os.remove(res_path)
|
229
|
+
else:
|
230
|
+
failures.append(res_path)
|
231
|
+
del calc
|
232
|
+
# read 2017_law.json reform file and specify its parameters dictionary
|
233
|
+
pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
|
234
|
+
pre_tcja = Policy.read_json_reform(pre_tcja_jrf)
|
235
|
+
# check reform file contents and reform results for each reform
|
236
|
+
reforms_path = os.path.join(tests_path, '..', 'reforms', '*.json')
|
237
|
+
json_reform_files = glob.glob(reforms_path)
|
238
|
+
for jrf in json_reform_files:
|
239
|
+
if jrf.endswith('ext.json'):
|
240
|
+
continue # skip ext.json, which is tested below in test_ext_reform
|
241
|
+
# determine reform's baseline by reading contents of jrf
|
242
|
+
with open(jrf, 'r') as rfile:
|
243
|
+
jrf_text = rfile.read()
|
244
|
+
pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text
|
245
|
+
# implement the reform relative to its baseline
|
246
|
+
reform = Policy.read_json_reform(jrf_text)
|
247
|
+
pol = Policy() # current-law policy
|
248
|
+
if pre_tcja_baseline:
|
249
|
+
pol.implement_reform(pre_tcja)
|
250
|
+
assert not pol.parameter_errors
|
251
|
+
pol.implement_reform(reform)
|
252
|
+
assert not pol.parameter_errors
|
253
|
+
calc = Calculator(policy=pol, records=cases, verbose=False)
|
254
|
+
calc.advance_to_year(tax_year)
|
255
|
+
calc.calc_all()
|
256
|
+
res_path = jrf.replace('.json', '.res.csv')
|
257
|
+
write_res_file(calc, res_path)
|
258
|
+
if res_and_out_are_same(res_path.replace('.res.csv', '')):
|
259
|
+
os.remove(res_path)
|
260
|
+
else:
|
261
|
+
failures.append(res_path)
|
262
|
+
del calc
|
263
|
+
if failures:
|
264
|
+
msg = 'Following reforms have res-vs-out differences:\n'
|
265
|
+
for ref in failures:
|
266
|
+
msg += '{}\n'.format(os.path.basename(ref))
|
267
|
+
raise ValueError(msg)
|
268
|
+
|
269
|
+
|
270
|
+
def reform_results(rid, reform_dict, puf_data, reform_2017_law):
|
271
|
+
"""
|
272
|
+
Return actual results of the reform specified by rid and reform_dict.
|
273
|
+
"""
|
274
|
+
# pylint: disable=too-many-locals
|
275
|
+
rec = Records(data=puf_data)
|
276
|
+
# create baseline Calculator object, calc1
|
277
|
+
pol = Policy()
|
278
|
+
if reform_dict['baseline'] == '2017_law.json':
|
279
|
+
pol.implement_reform(reform_2017_law)
|
280
|
+
elif reform_dict['baseline'] == 'policy_current_law.json':
|
281
|
+
pass
|
282
|
+
else:
|
283
|
+
msg = 'illegal baseline value {}'
|
284
|
+
raise ValueError(msg.format(reform_dict['baseline']))
|
285
|
+
calc1 = Calculator(policy=pol, records=rec, verbose=False)
|
286
|
+
# create reform Calculator object, calc2
|
287
|
+
start_year = reform_dict['start_year']
|
288
|
+
reform = dict()
|
289
|
+
for name, value in reform_dict['value'].items():
|
290
|
+
reform[name] = {start_year: value}
|
291
|
+
pol.implement_reform(reform)
|
292
|
+
calc2 = Calculator(policy=pol, records=rec, verbose=False)
|
293
|
+
# increment both Calculator objects to reform's start_year
|
294
|
+
calc1.advance_to_year(start_year)
|
295
|
+
calc2.advance_to_year(start_year)
|
296
|
+
# calculate baseline and reform output for several years
|
297
|
+
output_type = reform_dict['output_type']
|
298
|
+
num_years = 4
|
299
|
+
results = list()
|
300
|
+
for _ in range(0, num_years):
|
301
|
+
calc1.calc_all()
|
302
|
+
baseline = calc1.array(output_type)
|
303
|
+
calc2.calc_all()
|
304
|
+
reform = calc2.array(output_type)
|
305
|
+
diff = reform - baseline
|
306
|
+
weighted_sum_diff = (diff * calc1.array('s006')).sum() * 1.0e-9
|
307
|
+
results.append(weighted_sum_diff)
|
308
|
+
calc1.increment_year()
|
309
|
+
calc2.increment_year()
|
310
|
+
# write actual results to actual_str
|
311
|
+
actual_str = '{}'.format(rid)
|
312
|
+
for iyr in range(0, num_years):
|
313
|
+
actual_str += ',{:.1f}'.format(results[iyr])
|
314
|
+
return actual_str
|
315
|
+
|
316
|
+
|
317
|
+
@pytest.fixture(scope='module', name='baseline_2017_law')
|
318
|
+
def fixture_baseline_2017_law(tests_path):
|
319
|
+
"""
|
320
|
+
Read ../reforms/2017_law.json and return its policy dictionary.
|
321
|
+
"""
|
322
|
+
pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
|
323
|
+
return Policy.read_json_reform(pre_tcja_jrf)
|
324
|
+
|
325
|
+
|
326
|
+
@pytest.fixture(scope='module', name='reforms_dict')
|
327
|
+
def fixture_reforms_dict(tests_path):
|
328
|
+
"""
|
329
|
+
Read reforms.json and convert to dictionary.
|
330
|
+
"""
|
331
|
+
reforms_path = os.path.join(tests_path, 'reforms.json')
|
332
|
+
with open(reforms_path, 'r') as rfile:
|
333
|
+
rjson = rfile.read()
|
334
|
+
return json.loads(rjson)
|
335
|
+
|
336
|
+
|
337
|
+
NUM_REFORMS = 64 # when changing this also change num_reforms in conftest.py
|
338
|
+
|
339
|
+
|
340
|
+
@pytest.mark.requires_pufcsv
|
341
|
+
@pytest.mark.parametrize('rid', [i for i in range(1, NUM_REFORMS + 1)])
|
342
|
+
def test_reforms(rid, test_reforms_init, tests_path, baseline_2017_law,
|
343
|
+
reforms_dict, puf_subsample):
|
344
|
+
"""
|
345
|
+
Write actual reform results to files.
|
346
|
+
"""
|
347
|
+
# pylint: disable=too-many-arguments
|
348
|
+
assert test_reforms_init == NUM_REFORMS
|
349
|
+
actual = reform_results(rid, reforms_dict[str(rid)],
|
350
|
+
puf_subsample, baseline_2017_law)
|
351
|
+
afile_path = os.path.join(tests_path,
|
352
|
+
'reform_actual_{}.csv'.format(rid))
|
353
|
+
with open(afile_path, 'w') as afile:
|
354
|
+
afile.write('rid,res1,res2,res3,res4\n')
|
355
|
+
afile.write('{}\n'.format(actual))
|
356
|
+
|
357
|
+
|
358
|
+
@pytest.mark.extend_tcja
|
359
|
+
def test_ext_reform(tests_path):
|
360
|
+
"""
|
361
|
+
Test ext.json reform that extends TCJA beyond 2025.
|
362
|
+
"""
|
363
|
+
# test syntax of ext.json reform file
|
364
|
+
end = Policy()
|
365
|
+
end.set_year(2026)
|
366
|
+
ext = Policy()
|
367
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', 'ext.json')
|
368
|
+
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
369
|
+
rtext = rfile.read()
|
370
|
+
ext.implement_reform(Policy.read_json_reform(rtext))
|
371
|
+
assert not ext.parameter_warnings
|
372
|
+
ext.set_year(2026)
|
373
|
+
assert ext.II_em < end.II_em
|
374
|
+
# test tax output generated by ext.json reform file using public CPS data
|
375
|
+
recs = Records.cps_constructor()
|
376
|
+
calc_end = Calculator(policy=end, records=recs, verbose=False)
|
377
|
+
calc_end.advance_to_year(2026)
|
378
|
+
calc_end.calc_all()
|
379
|
+
iitax_end = calc_end.array('iitax')
|
380
|
+
calc_ext = Calculator(policy=ext, records=recs, verbose=False)
|
381
|
+
calc_ext.advance_to_year(2026)
|
382
|
+
calc_ext.calc_all()
|
383
|
+
iitax_ext = calc_ext.array('iitax')
|
384
|
+
rdiff = iitax_ext - iitax_end
|
385
|
+
weighted_sum_rdiff = (rdiff * calc_end.array('s006')).sum() * 1.0e-9
|
386
|
+
assert np.allclose([weighted_sum_rdiff], [-230.805], rtol=0.0, atol=0.01)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"""
|
2
|
+
Test example JSON response assumption files in taxcalc/responses directory
|
3
|
+
"""
|
4
|
+
# CODING-STYLE CHECKS:
|
5
|
+
# pycodestyle est_responses.py
|
6
|
+
# pylint --disable=locally-disabled test_responses.py
|
7
|
+
|
8
|
+
import os
|
9
|
+
import glob
|
10
|
+
# pylint: disable=import-error
|
11
|
+
from taxcalc import Consumption, GrowDiff
|
12
|
+
|
13
|
+
|
14
|
+
def test_response_json(tests_path):
|
15
|
+
"""
|
16
|
+
Check that each JSON file can be converted into dictionaries that
|
17
|
+
can be used to construct objects needed for a Calculator object.
|
18
|
+
"""
|
19
|
+
# pylint: disable=too-many-locals
|
20
|
+
responses_path = os.path.join(tests_path, '..', 'responses', '*.json')
|
21
|
+
for jpf in glob.glob(responses_path):
|
22
|
+
# read contents of jpf (JSON parameter filename)
|
23
|
+
jfile = open(jpf, 'r')
|
24
|
+
jpf_text = jfile.read()
|
25
|
+
# check that jpf_text can be used to construct objects
|
26
|
+
response_file = ('"consumption"' in jpf_text and
|
27
|
+
'"growdiff_baseline"' in jpf_text and
|
28
|
+
'"growdiff_response"' in jpf_text)
|
29
|
+
if response_file:
|
30
|
+
consumption = Consumption()
|
31
|
+
con_change = Consumption.read_json_update(jpf_text)
|
32
|
+
consumption.update_consumption(con_change)
|
33
|
+
del consumption
|
34
|
+
for topkey in ['growdiff_baseline', 'growdiff_response']:
|
35
|
+
growdiff = GrowDiff()
|
36
|
+
gdiff_change = GrowDiff.read_json_update(jpf_text, topkey)
|
37
|
+
growdiff.update_growdiff(gdiff_change)
|
38
|
+
del growdiff
|
39
|
+
else: # jpf_text is not a valid JSON response assumption file
|
40
|
+
print('test-failing-filename: ' + jpf)
|
41
|
+
assert False
|