taxcalc 5.2.0__py3-none-any.whl → 6.0.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 +3 -3
- taxcalc/calcfunctions.py +2 -2
- taxcalc/calculator.py +4 -4
- taxcalc/cli/tc.py +16 -19
- taxcalc/data.py +2 -3
- taxcalc/decorators.py +9 -8
- taxcalc/growfactors.py +2 -1
- taxcalc/policy.py +6 -23
- taxcalc/policy_current_law.json +31 -631
- taxcalc/records.py +78 -82
- taxcalc/records_variables.json +106 -106
- taxcalc/reforms/ARPA.out.csv +9 -9
- taxcalc/taxcalcio.py +101 -77
- taxcalc/tests/conftest.py +20 -15
- taxcalc/tests/puf_var_correl_coeffs_2016.csv +24 -24
- taxcalc/tests/puf_var_wght_means_by_year.csv +11 -11
- taxcalc/tests/pufcsv_agg_expect.csv +20 -20
- taxcalc/tests/pufcsv_mtr_expect.txt +21 -21
- taxcalc/tests/reforms.json +3 -1
- taxcalc/tests/reforms_expect.csv +54 -54
- taxcalc/tests/test_4package.py +8 -9
- taxcalc/tests/test_calculator.py +55 -18
- taxcalc/tests/test_consumption.py +2 -2
- taxcalc/tests/test_cpscsv.py +2 -24
- taxcalc/tests/test_data.py +11 -3
- taxcalc/tests/test_decorators.py +57 -52
- taxcalc/tests/test_growdiff.py +2 -2
- taxcalc/tests/test_parameters.py +101 -53
- taxcalc/tests/test_policy.py +154 -154
- taxcalc/tests/test_records.py +144 -9
- taxcalc/tests/test_reforms.py +104 -104
- taxcalc/tests/test_taxcalcio.py +13 -62
- taxcalc/utils.py +3 -3
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/METADATA +3 -6
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/RECORD +39 -46
- taxcalc/puf_ratios.csv +0 -26
- taxcalc/puf_weights.csv.gz +0 -0
- taxcalc/reforms/clp.out.csv +0 -10
- taxcalc/tests/test_compare.py +0 -330
- taxcalc/tests/test_compatible_data.py +0 -334
- taxcalc/tests/test_puf_var_stats.py +0 -194
- taxcalc/tests/test_pufcsv.py +0 -328
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/WHEEL +0 -0
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/entry_points.txt +0 -0
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/top_level.txt +0 -0
taxcalc/tests/test_records.py
CHANGED
@@ -29,10 +29,80 @@ def test_incorrect_records_instantiation(cps_subsample, cps_fullsample):
|
|
29
29
|
_ = Records(data=cps_subsample, gfactors=None, weights=None,
|
30
30
|
adjust_ratios=[])
|
31
31
|
# test error raise when num of records is greater than num of weights
|
32
|
-
|
33
|
-
|
32
|
+
cps_weights_path = os.path.join(Records.CODE_PATH, 'cps_weights.csv.gz')
|
33
|
+
weights = pd.read_csv(cps_weights_path)
|
34
|
+
some_wghts = weights[:100]
|
34
35
|
with pytest.raises(ValueError):
|
35
|
-
_ = Records(data=cps_fullsample, weights=
|
36
|
+
_ = Records(data=cps_fullsample, weights=some_wghts, start_year=2020)
|
37
|
+
|
38
|
+
|
39
|
+
def test_invalid_variable_values_1(cps_subsample):
|
40
|
+
"""Test docstring"""
|
41
|
+
dta = cps_subsample.copy()
|
42
|
+
dta['PT_SSTB_income'] = 2
|
43
|
+
with pytest.raises(ValueError):
|
44
|
+
_ = Records(data=dta, start_year=2000)
|
45
|
+
dta['e01700'] = 1000
|
46
|
+
with pytest.raises(ValueError):
|
47
|
+
_ = Records(data=dta, start_year=2000)
|
48
|
+
dta['e00650'] = 1000
|
49
|
+
with pytest.raises(ValueError):
|
50
|
+
_ = Records(data=dta, start_year=2000)
|
51
|
+
dta['k1bx14s'] = 1000
|
52
|
+
with pytest.raises(ValueError):
|
53
|
+
_ = Records(data=dta, start_year=2000)
|
54
|
+
dta['e02100'] = dta['e02100p'] + dta['e02100s'] + 1000
|
55
|
+
with pytest.raises(ValueError):
|
56
|
+
_ = Records(data=dta, start_year=2000)
|
57
|
+
dta['e00900'] = dta['e00900p'] + dta['e00900s'] + 1000
|
58
|
+
with pytest.raises(ValueError):
|
59
|
+
_ = Records(data=dta, start_year=2000)
|
60
|
+
dta['e00200'] = dta['e00200p'] + dta['e00200s'] + 1000
|
61
|
+
with pytest.raises(ValueError):
|
62
|
+
_ = Records(data=dta, start_year=2000)
|
63
|
+
dta['EIC'] = 4
|
64
|
+
with pytest.raises(ValueError):
|
65
|
+
_ = Records(data=dta, start_year=2000)
|
66
|
+
dta['MARS'] = 0
|
67
|
+
with pytest.raises(ValueError):
|
68
|
+
_ = Records(data=dta, start_year=2000)
|
69
|
+
|
70
|
+
|
71
|
+
def test_invalid_variable_values_2():
|
72
|
+
"""Test docstring"""
|
73
|
+
dta = pd.DataFrame(
|
74
|
+
{
|
75
|
+
'RECID': [1],
|
76
|
+
'MARS': [1],
|
77
|
+
'e00200p': [8e4],
|
78
|
+
'e00200s': [1e4],
|
79
|
+
'e00200': [9e4],
|
80
|
+
}
|
81
|
+
)
|
82
|
+
with pytest.raises(ValueError):
|
83
|
+
_ = Records(data=dta, start_year=2000)
|
84
|
+
dta = pd.DataFrame(
|
85
|
+
{
|
86
|
+
'RECID': [1],
|
87
|
+
'MARS': [1],
|
88
|
+
'e00900p': [8e4],
|
89
|
+
'e00900s': [1e4],
|
90
|
+
'e00900': [9e4],
|
91
|
+
}
|
92
|
+
)
|
93
|
+
with pytest.raises(ValueError):
|
94
|
+
_ = Records(data=dta, start_year=2000)
|
95
|
+
dta = pd.DataFrame(
|
96
|
+
{
|
97
|
+
'RECID': [1],
|
98
|
+
'MARS': [1],
|
99
|
+
'e02100p': [8e4],
|
100
|
+
'e02100s': [1e4],
|
101
|
+
'e02100': [9e4],
|
102
|
+
}
|
103
|
+
)
|
104
|
+
with pytest.raises(ValueError):
|
105
|
+
_ = Records(data=dta, start_year=2000)
|
36
106
|
|
37
107
|
|
38
108
|
def test_correct_records_instantiation(cps_subsample):
|
@@ -45,16 +115,15 @@ def test_correct_records_instantiation(cps_subsample):
|
|
45
115
|
rec1.increment_year()
|
46
116
|
sum_e00200_in_cps_year_plus_one = getattr(rec1, 'e00200').sum()
|
47
117
|
assert sum_e00200_in_cps_year_plus_one == sum_e00200_in_cps_year
|
48
|
-
wghts_path = os.path.join(Records.CODE_PATH,
|
118
|
+
wghts_path = os.path.join(Records.CODE_PATH, 'cps_weights.csv.gz')
|
49
119
|
wghts_df = pd.read_csv(wghts_path)
|
50
|
-
ratios_path = os.path.join(Records.CODE_PATH, Records.PUF_RATIOS_FILENAME)
|
51
|
-
ratios_df = pd.read_csv(ratios_path, index_col=0).transpose()
|
52
120
|
rec2 = Records(data=cps_subsample,
|
53
121
|
start_year=Records.CPSCSV_YEAR,
|
54
122
|
gfactors=GrowFactors(),
|
55
123
|
weights=wghts_df,
|
56
|
-
adjust_ratios=
|
57
|
-
exact_calculations=False
|
124
|
+
adjust_ratios=None,
|
125
|
+
exact_calculations=False,
|
126
|
+
weights_scale=0.01)
|
58
127
|
assert rec2
|
59
128
|
assert np.all(getattr(rec2, 'MARS') != 0)
|
60
129
|
assert getattr(rec2, 'current_year') == getattr(rec2, 'data_year')
|
@@ -66,7 +135,7 @@ def test_read_cps_data(cps_fullsample):
|
|
66
135
|
assert data.equals(cps_fullsample)
|
67
136
|
|
68
137
|
|
69
|
-
@pytest.mark.parametrize(
|
138
|
+
@pytest.mark.parametrize('csv', [
|
70
139
|
(
|
71
140
|
'RECID,MARS,e00200,e00200p,e00200s\n'
|
72
141
|
'1, 2, 200000, 200000, 0.03\n'
|
@@ -134,21 +203,25 @@ def test_read_data(csv):
|
|
134
203
|
def test_for_duplicate_names():
|
135
204
|
"""Test docstring"""
|
136
205
|
records_varinfo = Records(data=None)
|
206
|
+
num_vars = 0
|
137
207
|
varnames = set()
|
138
208
|
for varname in records_varinfo.USABLE_READ_VARS:
|
139
209
|
assert varname not in varnames
|
140
210
|
varnames.add(varname)
|
141
211
|
assert varname not in records_varinfo.CALCULATED_VARS
|
212
|
+
num_vars += len(varnames)
|
142
213
|
varnames = set()
|
143
214
|
for varname in records_varinfo.CALCULATED_VARS:
|
144
215
|
assert varname not in varnames
|
145
216
|
varnames.add(varname)
|
146
217
|
assert varname not in records_varinfo.USABLE_READ_VARS
|
218
|
+
num_vars += len(varnames)
|
147
219
|
varnames = set()
|
148
220
|
for varname in records_varinfo.INTEGER_READ_VARS:
|
149
221
|
assert varname not in varnames
|
150
222
|
varnames.add(varname)
|
151
223
|
assert varname in records_varinfo.USABLE_READ_VARS
|
224
|
+
assert num_vars == 212 # number of vars in records_variables.json
|
152
225
|
|
153
226
|
|
154
227
|
def test_records_variables_content(tests_path):
|
@@ -245,3 +318,65 @@ def test_csv_input_vars_md_contents(tests_path):
|
|
245
318
|
for var in valid_less_civ:
|
246
319
|
msg += f'VARIABLE= {var}\n' # pylint: disable=consider-using-join
|
247
320
|
raise ValueError(msg)
|
321
|
+
|
322
|
+
|
323
|
+
def test_cps_availability(tests_path, cps_data_path):
|
324
|
+
"""
|
325
|
+
Cross-check records_variables.json data with variables in cps.csv file.
|
326
|
+
"""
|
327
|
+
# make set of variable names that are in the cps.csv file
|
328
|
+
cpsdf = pd.read_csv(cps_data_path)
|
329
|
+
cpsvars = set(sorted(list(cpsdf)))
|
330
|
+
# make set of variable names that are marked as cps available in r_v.json
|
331
|
+
rvpath = os.path.join(tests_path, '..', 'records_variables.json')
|
332
|
+
with open(rvpath, 'r', encoding='utf-8') as rvfile:
|
333
|
+
rvdict = json.load(rvfile)
|
334
|
+
recvars = set()
|
335
|
+
for vname, vdict in rvdict['read'].items():
|
336
|
+
if 'taxdata_cps' in vdict.get('availability', ''):
|
337
|
+
recvars.add(vname)
|
338
|
+
# check that cpsvars and recvars sets are the same
|
339
|
+
assert (cpsvars - recvars) == set()
|
340
|
+
assert (recvars - cpsvars) == set()
|
341
|
+
|
342
|
+
|
343
|
+
@pytest.mark.requires_puf
|
344
|
+
def test_puf_availability(tests_path, puf_data_path):
|
345
|
+
"""
|
346
|
+
Cross-check records_variables.json data with variables in puf.csv file
|
347
|
+
"""
|
348
|
+
# make set of variable names that are in the puf.csv file
|
349
|
+
pufdf = pd.read_csv(puf_data_path)
|
350
|
+
pufvars = set(sorted(list(pufdf)))
|
351
|
+
# make set of variable names that are marked as puf available in r_v.json
|
352
|
+
rvpath = os.path.join(tests_path, '..', 'records_variables.json')
|
353
|
+
with open(rvpath, 'r', encoding='utf-8') as rvfile:
|
354
|
+
rvdict = json.load(rvfile)
|
355
|
+
recvars = set()
|
356
|
+
for vname, vdict in rvdict['read'].items():
|
357
|
+
if 'taxdata_puf' in vdict.get('availability', ''):
|
358
|
+
recvars.add(vname)
|
359
|
+
# check that pufvars and recvars sets are the same
|
360
|
+
assert (pufvars - recvars) == set()
|
361
|
+
assert (recvars - pufvars) == set()
|
362
|
+
|
363
|
+
|
364
|
+
@pytest.mark.requires_tmd
|
365
|
+
def test_tmd_availability(tests_path, tmd_data_path):
|
366
|
+
"""
|
367
|
+
Cross-check records_variables.json data with variables in tmd.csv file
|
368
|
+
"""
|
369
|
+
# make set of variable names that are in the tmd.csv file
|
370
|
+
tmddf = pd.read_csv(tmd_data_path)
|
371
|
+
tmdvars = set(sorted(list(tmddf)))
|
372
|
+
# make set of variable names that are marked as tmd available in r_v.json
|
373
|
+
rvpath = os.path.join(tests_path, '..', 'records_variables.json')
|
374
|
+
with open(rvpath, 'r', encoding='utf-8') as rvfile:
|
375
|
+
rvdict = json.load(rvfile)
|
376
|
+
recvars = set()
|
377
|
+
for vname, vdict in rvdict['read'].items():
|
378
|
+
if 'taxmicrodata_tmd' in vdict.get('availability', ''):
|
379
|
+
recvars.add(vname)
|
380
|
+
# check that tmdvars and recvars sets are the same
|
381
|
+
assert (tmdvars - recvars) == set()
|
382
|
+
assert (recvars - tmdvars) == set()
|
taxcalc/tests/test_reforms.py
CHANGED
@@ -73,8 +73,19 @@ def test_2017_law_reform(tests_path):
|
|
73
73
|
assert act == exp, f'{name} a={act} != e={exp}'
|
74
74
|
|
75
75
|
|
76
|
+
def _apply_reform(policy, reform_path):
|
77
|
+
"""
|
78
|
+
Helper function to apply a reform and assert no errors.
|
79
|
+
"""
|
80
|
+
with open(reform_path, 'r', encoding='utf-8') as rfile:
|
81
|
+
rtext = rfile.read()
|
82
|
+
policy.implement_reform(Policy.read_json_reform(rtext))
|
83
|
+
assert not policy.parameter_errors
|
84
|
+
assert not policy.errors
|
85
|
+
|
86
|
+
|
76
87
|
@pytest.mark.rtr
|
77
|
-
@pytest.mark.parametrize('fyear', [2019, 2020, 2021, 2022, 2023])
|
88
|
+
@pytest.mark.parametrize('fyear', [2019, 2020, 2021, 2022, 2023, 2024, 2025])
|
78
89
|
def test_round_trip_reforms(fyear, tests_path):
|
79
90
|
"""
|
80
91
|
Check that current-law policy has the same policy parameter values in
|
@@ -83,7 +94,7 @@ def test_round_trip_reforms(fyear, tests_path):
|
|
83
94
|
reforms that represents new tax legislation since 2017.
|
84
95
|
This test checks that the future-year parameter values for
|
85
96
|
current-law policy (which incorporates recent legislation such as
|
86
|
-
the TCJA, CARES Act, and
|
97
|
+
the TCJA, CARES Act, ARPA, and OBBBA) are the same as future-year
|
87
98
|
parameter values for the compound round-trip reform.
|
88
99
|
Doing this check ensures that the 2017_law.json
|
89
100
|
and subsequent reform files that represent recent legislation are
|
@@ -97,48 +108,21 @@ def test_round_trip_reforms(fyear, tests_path):
|
|
97
108
|
clp_mdata = dict(clp_pol.items())
|
98
109
|
# create rtr metadata dictionary for round-trip reform in fyear
|
99
110
|
rtr_pol = Policy()
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
reform_file = os.path.join(tests_path, '..', 'reforms', 'CARES.json')
|
116
|
-
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
117
|
-
rtext = rfile.read()
|
118
|
-
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
119
|
-
# Layer on the Consolidated Appropriations Act of 2021
|
120
|
-
reform_file = os.path.join(
|
121
|
-
tests_path, '..', 'reforms', 'ConsolidatedAppropriationsAct2021.json'
|
122
|
-
)
|
123
|
-
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
124
|
-
rtext = rfile.read()
|
125
|
-
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
126
|
-
assert not rtr_pol.parameter_errors
|
127
|
-
assert not rtr_pol.errors
|
128
|
-
# Layer on ARPA
|
129
|
-
reform_file = os.path.join(tests_path, '..', 'reforms', 'ARPA.json')
|
130
|
-
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
131
|
-
rtext = rfile.read()
|
132
|
-
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
133
|
-
assert not rtr_pol.parameter_errors
|
134
|
-
assert not rtr_pol.errors
|
135
|
-
# Layer on rounding from IRS through Policy.LAST_KNOWN_YEAR
|
136
|
-
reform_file = os.path.join(tests_path, '..', 'reforms', 'rounding.json')
|
137
|
-
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
138
|
-
rtext = rfile.read()
|
139
|
-
rtr_pol.implement_reform(Policy.read_json_reform(rtext))
|
140
|
-
assert not rtr_pol.parameter_errors
|
141
|
-
assert not rtr_pol.errors
|
111
|
+
|
112
|
+
reform_files_to_apply = [
|
113
|
+
'2017_law.json',
|
114
|
+
'TCJA.json',
|
115
|
+
'CARES.json',
|
116
|
+
'ConsolidatedAppropriationsAct2021.json',
|
117
|
+
'ARPA.json',
|
118
|
+
'OBBBA.json',
|
119
|
+
'rounding.json'
|
120
|
+
]
|
121
|
+
for reform_filename in reform_files_to_apply:
|
122
|
+
reform_file_path = os.path.join(tests_path, '..',
|
123
|
+
'reforms', reform_filename)
|
124
|
+
_apply_reform(rtr_pol, reform_file_path)
|
125
|
+
|
142
126
|
rtr_pol.set_year(fyear)
|
143
127
|
rtr_mdata = dict(rtr_pol.items())
|
144
128
|
# compare fyear policy parameter values
|
@@ -158,7 +142,7 @@ def test_round_trip_reforms(fyear, tests_path):
|
|
158
142
|
clp_val = clp_mdata[pname]
|
159
143
|
if not np.allclose(rtr_val, clp_val):
|
160
144
|
fail_params.append(pname)
|
161
|
-
msg += '\n {pname} in {fyear} : rtr={rtr_val} clp={clp_val}'
|
145
|
+
msg += f'\n {pname} in {fyear} : rtr={rtr_val} clp={clp_val}'
|
162
146
|
if fail_dump:
|
163
147
|
rtr_fails.write(f'{pname} {fyear} {rtr_val}\n')
|
164
148
|
clp_fails.write(f'{pname} {fyear} {clp_val}\n')
|
@@ -169,8 +153,23 @@ def test_round_trip_reforms(fyear, tests_path):
|
|
169
153
|
raise ValueError(msg)
|
170
154
|
|
171
155
|
|
156
|
+
REFORM_DIR = os.path.join(os.path.dirname(__file__), '..', 'reforms')
|
157
|
+
REFORM_FILES = glob.glob(os.path.join(REFORM_DIR, '*.json'))
|
158
|
+
REFORM_YEARS = {
|
159
|
+
'ARPA.json': 2022,
|
160
|
+
'ext.json': 2026,
|
161
|
+
'NoOBBBA.json': 2026,
|
162
|
+
'OBBBA.json': 2026,
|
163
|
+
}
|
164
|
+
|
165
|
+
|
172
166
|
@pytest.mark.reforms
|
173
|
-
|
167
|
+
@pytest.mark.parametrize(
|
168
|
+
'reform_file,tax_year',
|
169
|
+
[(os.path.basename(f), REFORM_YEARS.get(os.path.basename(f), 2020))
|
170
|
+
for f in REFORM_FILES],
|
171
|
+
)
|
172
|
+
def test_reform_json_and_output(reform_file, tax_year, tests_path):
|
174
173
|
"""
|
175
174
|
Check that each JSON reform file can be converted into a reform dictionary
|
176
175
|
that can then be passed to the Policy class implement_reform method that
|
@@ -213,7 +212,6 @@ def test_reform_json_and_output(tests_path):
|
|
213
212
|
return not diffs
|
214
213
|
|
215
214
|
# specify Records object containing cases data
|
216
|
-
tax_year = 2020
|
217
215
|
cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv')
|
218
216
|
cases = Records(data=cases_path,
|
219
217
|
start_year=tax_year, # set raw input data year
|
@@ -226,9 +224,10 @@ def test_reform_json_and_output(tests_path):
|
|
226
224
|
calc = Calculator(policy=Policy(), records=cases, verbose=False)
|
227
225
|
calc.advance_to_year(tax_year)
|
228
226
|
calc.calc_all()
|
229
|
-
|
227
|
+
clp_base = cases_path.replace('cases.csv', f'clp-{tax_year}')
|
228
|
+
res_path = clp_base + '.res.csv'
|
230
229
|
write_res_file(calc, res_path)
|
231
|
-
if res_and_out_are_same(
|
230
|
+
if res_and_out_are_same(clp_base):
|
232
231
|
os.remove(res_path)
|
233
232
|
else:
|
234
233
|
failures.append(res_path)
|
@@ -237,33 +236,29 @@ def test_reform_json_and_output(tests_path):
|
|
237
236
|
pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
|
238
237
|
pre_tcja = Policy.read_json_reform(pre_tcja_jrf)
|
239
238
|
# check reform file contents and reform results for each reform
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
reform = Policy.read_json_reform(jrf_text)
|
251
|
-
pol = Policy() # current-law policy
|
252
|
-
if pre_tcja_baseline:
|
253
|
-
pol.implement_reform(pre_tcja)
|
254
|
-
assert not pol.parameter_errors
|
255
|
-
pol.implement_reform(reform)
|
239
|
+
jrf = os.path.join(tests_path, '..', 'reforms', reform_file)
|
240
|
+
# determine reform's baseline by reading contents of jrf
|
241
|
+
with open(jrf, 'r', encoding='utf-8') as rfile:
|
242
|
+
jrf_text = rfile.read()
|
243
|
+
pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text
|
244
|
+
# implement the reform relative to its baseline
|
245
|
+
reform = Policy.read_json_reform(jrf_text)
|
246
|
+
pol = Policy() # current-law policy
|
247
|
+
if pre_tcja_baseline:
|
248
|
+
pol.implement_reform(pre_tcja)
|
256
249
|
assert not pol.parameter_errors
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
250
|
+
pol.implement_reform(reform)
|
251
|
+
assert not pol.parameter_errors
|
252
|
+
calc = Calculator(policy=pol, records=cases, verbose=False)
|
253
|
+
calc.advance_to_year(tax_year)
|
254
|
+
calc.calc_all()
|
255
|
+
res_path = jrf.replace('.json', '.res.csv')
|
256
|
+
write_res_file(calc, res_path)
|
257
|
+
if res_and_out_are_same(res_path.replace('.res.csv', '')):
|
258
|
+
os.remove(res_path)
|
259
|
+
else:
|
260
|
+
failures.append(res_path)
|
261
|
+
del calc
|
267
262
|
if failures:
|
268
263
|
msg = 'Following reforms have res-vs-out differences:\n'
|
269
264
|
for ref in failures:
|
@@ -271,12 +266,12 @@ def test_reform_json_and_output(tests_path):
|
|
271
266
|
raise ValueError(msg)
|
272
267
|
|
273
268
|
|
274
|
-
def reform_results(rid, reform_dict,
|
269
|
+
def reform_results(rid, reform_dict, cps_data, reform_2017_law):
|
275
270
|
"""
|
276
271
|
Return actual results of the reform specified by rid and reform_dict.
|
277
272
|
"""
|
278
273
|
# pylint: disable=too-many-locals
|
279
|
-
rec = Records(data=
|
274
|
+
rec = Records.cps_constructor(data=cps_data)
|
280
275
|
# create baseline Calculator object, calc1
|
281
276
|
pol = Policy()
|
282
277
|
if reform_dict['baseline'] == '2017_law.json':
|
@@ -341,49 +336,54 @@ def fixture_reforms_dict(tests_path):
|
|
341
336
|
NUM_REFORMS = 60 # when changing this also change num_reforms in conftest.py
|
342
337
|
|
343
338
|
|
344
|
-
@pytest.mark.requires_pufcsv
|
345
339
|
@pytest.mark.parametrize('rid', list(range(1, NUM_REFORMS + 1)))
|
346
340
|
def test_reforms(rid, test_reforms_init, tests_path, baseline_2017_law,
|
347
|
-
reforms_dict,
|
341
|
+
reforms_dict, cps_subsample):
|
348
342
|
"""
|
349
343
|
Write actual reform results to files.
|
350
344
|
"""
|
351
345
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
352
346
|
assert test_reforms_init == NUM_REFORMS
|
353
347
|
actual = reform_results(rid, reforms_dict[str(rid)],
|
354
|
-
|
348
|
+
cps_subsample, baseline_2017_law)
|
355
349
|
afile_path = os.path.join(tests_path, f'reform_actual_{rid}.csv')
|
356
350
|
with open(afile_path, 'w', encoding='utf-8') as afile:
|
357
351
|
afile.write('rid,res1,res2,res3,res4\n')
|
358
352
|
afile.write(f'{actual}\n')
|
359
353
|
|
360
354
|
|
361
|
-
@pytest.mark.
|
362
|
-
|
355
|
+
@pytest.mark.parametrize('reform_filename, expected_diff', [
|
356
|
+
('ext.json', 45.491),
|
357
|
+
('OBBBA.json', 0.0),
|
358
|
+
('NoOBBBA.json', 292.402),
|
359
|
+
])
|
360
|
+
def test_reforms_cps(reform_filename, expected_diff, tests_path):
|
363
361
|
"""
|
364
|
-
Test
|
362
|
+
Test reforms beyond 2025 using public CPS data.
|
365
363
|
"""
|
366
|
-
|
367
|
-
|
368
|
-
end.set_year(2026)
|
369
|
-
ext = Policy()
|
370
|
-
reform_file = os.path.join(tests_path, '..', 'reforms', 'ext.json')
|
364
|
+
pol = Policy()
|
365
|
+
reform_file = os.path.join(tests_path, '..', 'reforms', reform_filename)
|
371
366
|
with open(reform_file, 'r', encoding='utf-8') as rfile:
|
372
367
|
rtext = rfile.read()
|
373
|
-
|
374
|
-
assert not
|
375
|
-
|
376
|
-
assert np.allclose([ext.II_em], [end.II_em])
|
377
|
-
# test tax output generated by ext.json reform file using public CPS data
|
368
|
+
pol.implement_reform(Policy.read_json_reform(rtext))
|
369
|
+
assert not pol.parameter_errors
|
370
|
+
|
378
371
|
recs = Records.cps_constructor()
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
372
|
+
|
373
|
+
# create a Calculator object using current-law policy
|
374
|
+
calc_clp = Calculator(policy=Policy(), records=recs, verbose=False)
|
375
|
+
calc_clp.advance_to_year(2026)
|
376
|
+
calc_clp.calc_all()
|
377
|
+
iitax_clp = calc_clp.array('iitax')
|
378
|
+
|
379
|
+
# create a Calculator object using the reform
|
380
|
+
calc_ref = Calculator(policy=pol, records=recs, verbose=False)
|
381
|
+
calc_ref.advance_to_year(2026)
|
382
|
+
calc_ref.calc_all()
|
383
|
+
iitax_ref = calc_ref.array('iitax')
|
384
|
+
|
385
|
+
# compare aggregate individual income tax liability
|
386
|
+
rdiff = iitax_ref - iitax_clp
|
387
|
+
weighted_sum_rdiff = (rdiff * calc_clp.array('s006')).sum() * 1.0e-9
|
388
|
+
assert np.allclose([weighted_sum_rdiff], [expected_diff],
|
389
|
+
rtol=0.0, atol=0.01)
|
taxcalc/tests/test_taxcalcio.py
CHANGED
@@ -320,50 +320,10 @@ def test_init_errors(reformfile0, errorreformfile, errorassumpfile,
|
|
320
320
|
# test TaxCalcIO.init method
|
321
321
|
tcio.init(input_data=recdf, tax_year=year,
|
322
322
|
baseline=baseline, reform=reform, assump=assump,
|
323
|
-
aging_input_data=False,
|
324
323
|
exact_calculations=True)
|
325
324
|
assert tcio.errmsg
|
326
325
|
|
327
326
|
|
328
|
-
def test_creation_with_aging(reformfile0):
|
329
|
-
"""
|
330
|
-
Test TaxCalcIO instantiation with/without no policy reform and with aging.
|
331
|
-
"""
|
332
|
-
taxyear = 2021
|
333
|
-
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
334
|
-
tax_year=taxyear,
|
335
|
-
baseline=None,
|
336
|
-
reform=reformfile0.name,
|
337
|
-
assump=None,
|
338
|
-
silent=False)
|
339
|
-
assert not tcio.errmsg
|
340
|
-
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
341
|
-
tax_year=taxyear,
|
342
|
-
baseline=None,
|
343
|
-
reform=reformfile0.name,
|
344
|
-
assump=None,
|
345
|
-
aging_input_data=True,
|
346
|
-
exact_calculations=False)
|
347
|
-
assert not tcio.errmsg
|
348
|
-
assert tcio.tax_year() == taxyear
|
349
|
-
taxyear = 2016
|
350
|
-
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
351
|
-
tax_year=taxyear,
|
352
|
-
baseline=None,
|
353
|
-
reform=None,
|
354
|
-
assump=None)
|
355
|
-
assert not tcio.errmsg
|
356
|
-
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
357
|
-
tax_year=taxyear,
|
358
|
-
baseline=None,
|
359
|
-
reform=None,
|
360
|
-
assump=None,
|
361
|
-
aging_input_data=True,
|
362
|
-
exact_calculations=False)
|
363
|
-
assert not tcio.errmsg
|
364
|
-
assert tcio.tax_year() == taxyear
|
365
|
-
|
366
|
-
|
367
327
|
def test_ctor_init_with_cps_files():
|
368
328
|
"""
|
369
329
|
Test use of CPS input files.
|
@@ -372,32 +332,32 @@ def test_ctor_init_with_cps_files():
|
|
372
332
|
txyr = 2020
|
373
333
|
for rid in [0, 99]:
|
374
334
|
tcio = TaxCalcIO('cps.csv', txyr, None, None, None, runid=rid)
|
375
|
-
tcio.init(
|
376
|
-
'cps.csv', txyr, None, None, None,
|
377
|
-
aging_input_data=True,
|
378
|
-
exact_calculations=False,
|
379
|
-
)
|
335
|
+
tcio.init('cps.csv', txyr, None, None, None, exact_calculations=False)
|
380
336
|
assert not tcio.errmsg
|
381
337
|
assert tcio.tax_year() == txyr
|
382
338
|
# test advance_to_year method
|
383
339
|
tcio.silent = False
|
384
|
-
tcio.advance_to_year(txyr + 1
|
340
|
+
tcio.advance_to_year(txyr + 1)
|
385
341
|
assert tcio.tax_year() == txyr + 1
|
386
342
|
# specify invalid tax_year for cps.csv input data
|
387
343
|
txyr = 2013
|
388
344
|
tcio = TaxCalcIO('cps.csv', txyr, None, None, None)
|
389
|
-
tcio.init('cps.csv', txyr, None, None, None,
|
390
|
-
aging_input_data=True,
|
391
|
-
exact_calculations=False)
|
345
|
+
tcio.init('cps.csv', txyr, None, None, None, exact_calculations=False)
|
392
346
|
assert tcio.errmsg
|
393
347
|
|
394
348
|
|
395
|
-
@pytest.mark.parametrize(
|
349
|
+
@pytest.mark.parametrize('dumpvar_str, str_valid, num_vars', [
|
396
350
|
("""
|
397
351
|
MARS;iitax payrolltax|combined,
|
398
352
|
c00100
|
399
353
|
surtax
|
400
|
-
""", True, 6), # these 6
|
354
|
+
""", True, 6), # these 6 variables minus MARS plus RECID
|
355
|
+
|
356
|
+
('ALL', True, 209),
|
357
|
+
# 209 =
|
358
|
+
# all 212 vars in records_variables.json (see test_records.py)
|
359
|
+
# minus 5 TaxCalcIO.BASE_DUMPVARS omitting RECID (see taxcalcio.py)
|
360
|
+
# plus 2 TaxCalcIO.MTR_DUMPVARS (see taxcalcio.py)
|
401
361
|
|
402
362
|
("""
|
403
363
|
MARS;iitax payrolltax|kombined,c00100
|
@@ -418,7 +378,6 @@ def test_dump_variables(dumpvar_str, str_valid, num_vars):
|
|
418
378
|
assert not tcio.errmsg
|
419
379
|
tcio.init(input_data=recdf, tax_year=year,
|
420
380
|
baseline=None, reform=None, assump=None,
|
421
|
-
aging_input_data=False,
|
422
381
|
exact_calculations=False)
|
423
382
|
assert not tcio.errmsg
|
424
383
|
varlist = tcio.dump_variables(dumpvar_str)
|
@@ -445,7 +404,6 @@ def test_output_options_min(reformfile1, assumpfile1):
|
|
445
404
|
baseline=None,
|
446
405
|
reform=reformfile1.name,
|
447
406
|
assump=assumpfile1.name,
|
448
|
-
aging_input_data=False,
|
449
407
|
exact_calculations=False)
|
450
408
|
assert not tcio.errmsg
|
451
409
|
dumppath = tcio.output_filepath().replace('.xxx', '.dumpdb')
|
@@ -480,7 +438,6 @@ def test_output_options_mtr(reformfile1, assumpfile1):
|
|
480
438
|
baseline=None,
|
481
439
|
reform=reformfile1.name,
|
482
440
|
assump=assumpfile1.name,
|
483
|
-
aging_input_data=False,
|
484
441
|
exact_calculations=False)
|
485
442
|
assert not tcio.errmsg
|
486
443
|
dumppath = tcio.output_filepath().replace('.xxx', '.dumpdb')
|
@@ -520,7 +477,6 @@ def test_write_policy_param_files(reformfile1):
|
|
520
477
|
baseline=compound_reform,
|
521
478
|
reform=compound_reform,
|
522
479
|
assump=None,
|
523
|
-
aging_input_data=False,
|
524
480
|
exact_calculations=False)
|
525
481
|
assert not tcio.errmsg
|
526
482
|
tcio.write_policy_params_files()
|
@@ -557,7 +513,6 @@ def test_no_tables_or_graphs(reformfile1):
|
|
557
513
|
baseline=None,
|
558
514
|
reform=reformfile1.name,
|
559
515
|
assump=None,
|
560
|
-
aging_input_data=False,
|
561
516
|
exact_calculations=False)
|
562
517
|
assert not tcio.errmsg
|
563
518
|
# create several TaxCalcIO output files
|
@@ -593,7 +548,6 @@ def test_tables(reformfile1):
|
|
593
548
|
baseline=None,
|
594
549
|
reform=reformfile1.name,
|
595
550
|
assump=None,
|
596
|
-
aging_input_data=False,
|
597
551
|
exact_calculations=False)
|
598
552
|
assert not tcio.errmsg
|
599
553
|
# create TaxCalcIO tables file
|
@@ -627,7 +581,6 @@ def test_graphs(reformfile1):
|
|
627
581
|
baseline=None,
|
628
582
|
reform=reformfile1.name,
|
629
583
|
assump=None,
|
630
|
-
aging_input_data=False,
|
631
584
|
exact_calculations=False)
|
632
585
|
assert not tcio.errmsg
|
633
586
|
tcio.analyze(output_graphs=True)
|
@@ -671,7 +624,6 @@ def test_analyze_warnings_print(warnreformfile):
|
|
671
624
|
baseline=None,
|
672
625
|
reform=warnreformfile.name,
|
673
626
|
assump=None,
|
674
|
-
aging_input_data=False,
|
675
627
|
exact_calculations=False)
|
676
628
|
assert not tcio.errmsg
|
677
629
|
tcio.analyze()
|
@@ -739,11 +691,10 @@ def test_error_message_parsed_correctly(regression_reform_file):
|
|
739
691
|
baseline=regression_reform_file.name,
|
740
692
|
reform=regression_reform_file.name,
|
741
693
|
assump=None,
|
742
|
-
aging_input_data=False,
|
743
694
|
exact_calculations=False)
|
744
695
|
assert isinstance(tcio.errmsg, str) and tcio.errmsg
|
745
696
|
exp_errmsg = (
|
746
|
-
|
747
|
-
|
697
|
+
'AMEDT_rt[year=2021] 1.8 > max 1 \n'
|
698
|
+
'AMEDT_rt[year=2021] 1.8 > max 1 '
|
748
699
|
)
|
749
700
|
assert tcio.errmsg == exp_errmsg
|