taxcalc 4.2.1__py3-none-any.whl → 4.2.2__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_current_law.json +2033 -184
- 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/reforms/rounding2022.json +153 -0
- taxcalc/reforms/rounding2022.out.csv +10 -0
- 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 +1575 -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 +385 -0
- taxcalc/tests/test_responses.py +41 -0
- taxcalc/tests/test_taxcalcio.py +755 -0
- taxcalc/tests/test_tmdcsv.py +38 -0
- taxcalc/tests/test_utils.py +792 -0
- taxcalc/tmd_growfactors.csv +54 -54
- taxcalc/tmd_weights.csv.gz +0 -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.2.2.dist-info}/METADATA +3 -4
- taxcalc-4.2.2.dist-info/RECORD +144 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/WHEEL +1 -1
- taxcalc-4.2.1.dist-info/RECORD +0 -34
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/LICENSE +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/entry_points.txt +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,755 @@
|
|
1
|
+
"""
|
2
|
+
Tests for Tax-Calculator TaxCalcIO class.
|
3
|
+
"""
|
4
|
+
# CODING-STYLE CHECKS:
|
5
|
+
# pycodestyle test_taxcalcio.py
|
6
|
+
# pylint --disable=locally-disabled test_taxcalcio.py
|
7
|
+
#
|
8
|
+
# pylint: disable=too-many-lines
|
9
|
+
|
10
|
+
import os
|
11
|
+
from io import StringIO
|
12
|
+
import tempfile
|
13
|
+
import pytest
|
14
|
+
import pandas as pd
|
15
|
+
from taxcalc import TaxCalcIO # pylint: disable=import-error
|
16
|
+
|
17
|
+
|
18
|
+
RAWINPUT = (
|
19
|
+
'RECID,MARS\n'
|
20
|
+
' 1, 2\n'
|
21
|
+
' 2, 1\n'
|
22
|
+
' 3, 4\n'
|
23
|
+
' 4, 3\n'
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.fixture(scope='module', name='reformfile0')
|
28
|
+
def fixture_reformfile0():
|
29
|
+
"""
|
30
|
+
Specify JSON reform file.
|
31
|
+
"""
|
32
|
+
txt = """
|
33
|
+
{ "policy": {
|
34
|
+
"SS_Earnings_c": {"2016": 300000,
|
35
|
+
"2018": 500000,
|
36
|
+
"2020": 700000}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
"""
|
40
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
41
|
+
rfile.write(txt + '\n')
|
42
|
+
rfile.close()
|
43
|
+
yield rfile
|
44
|
+
if os.path.isfile(rfile.name):
|
45
|
+
try:
|
46
|
+
os.remove(rfile.name)
|
47
|
+
except OSError:
|
48
|
+
pass # sometimes we can't remove a generated temporary file
|
49
|
+
|
50
|
+
|
51
|
+
@pytest.fixture(scope='module', name='assumpfile0')
|
52
|
+
def fixture_assumpfile0():
|
53
|
+
"""
|
54
|
+
Temporary assumption file with .json extension.
|
55
|
+
"""
|
56
|
+
afile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
57
|
+
contents = """
|
58
|
+
{
|
59
|
+
"consumption": {},
|
60
|
+
"growdiff_baseline": {"ABOOK": {"2015": -0.01}},
|
61
|
+
"growdiff_response": {}
|
62
|
+
}
|
63
|
+
"""
|
64
|
+
afile.write(contents)
|
65
|
+
afile.close()
|
66
|
+
yield afile
|
67
|
+
if os.path.isfile(afile.name):
|
68
|
+
try:
|
69
|
+
os.remove(afile.name)
|
70
|
+
except OSError:
|
71
|
+
pass # sometimes we can't remove a generated temporary file
|
72
|
+
|
73
|
+
|
74
|
+
@pytest.fixture(scope='module', name='reformfile1')
|
75
|
+
def fixture_reformfile1():
|
76
|
+
"""
|
77
|
+
Temporary reform file with .json extension.
|
78
|
+
"""
|
79
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
80
|
+
contents = """
|
81
|
+
{"policy": {
|
82
|
+
"AMT_brk1": { // top of first AMT tax bracket
|
83
|
+
"2015": 200000,
|
84
|
+
"2017": 300000},
|
85
|
+
"EITC_c": { // max EITC amount by number of qualifying kids (0,1,2,3+)
|
86
|
+
"2016": [ 900, 5000, 8000, 9000],
|
87
|
+
"2019": [1200, 7000, 10000, 12000]},
|
88
|
+
"II_em": { // personal exemption amount (see indexing changes below)
|
89
|
+
"2016": 6000,
|
90
|
+
"2018": 7500,
|
91
|
+
"2020": 9000},
|
92
|
+
"II_em-indexed": { // personal exemption amount indexing status
|
93
|
+
"2016": false, // values in future years are same as this year value
|
94
|
+
"2018": true // values in future years indexed with this year as base
|
95
|
+
},
|
96
|
+
"SS_Earnings_c": { // social security (OASDI) maximum taxable earnings
|
97
|
+
"2016": 300000,
|
98
|
+
"2018": 500000,
|
99
|
+
"2020": 700000},
|
100
|
+
"AMT_em-indexed": { // AMT exemption amount indexing status
|
101
|
+
"2017": false, // values in future years are same as this year value
|
102
|
+
"2020": true // values in future years indexed with this year as base
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
"""
|
107
|
+
rfile.write(contents)
|
108
|
+
rfile.close()
|
109
|
+
yield rfile
|
110
|
+
if os.path.isfile(rfile.name):
|
111
|
+
try:
|
112
|
+
os.remove(rfile.name)
|
113
|
+
except OSError:
|
114
|
+
pass # sometimes we can't remove a generated temporary file
|
115
|
+
|
116
|
+
|
117
|
+
@pytest.fixture(scope='module', name='baselinebad')
|
118
|
+
def fixture_baselinebad():
|
119
|
+
"""
|
120
|
+
Temporary baseline file with .json extension.
|
121
|
+
"""
|
122
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
123
|
+
contents = '{ "policy": {"AMT_brk1": {"2011": 0.0}}}'
|
124
|
+
rfile.write(contents)
|
125
|
+
rfile.close()
|
126
|
+
yield rfile
|
127
|
+
if os.path.isfile(rfile.name):
|
128
|
+
try:
|
129
|
+
os.remove(rfile.name)
|
130
|
+
except OSError:
|
131
|
+
pass # sometimes we can't remove a generated temporary file
|
132
|
+
|
133
|
+
|
134
|
+
@pytest.fixture(scope='module', name='errorreformfile')
|
135
|
+
def fixture_errorreformfile():
|
136
|
+
"""
|
137
|
+
Temporary reform file with .json extension.
|
138
|
+
"""
|
139
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
140
|
+
contents = '{ "policy": {"xxx": {"2015": 0}}}'
|
141
|
+
rfile.write(contents)
|
142
|
+
rfile.close()
|
143
|
+
yield rfile
|
144
|
+
if os.path.isfile(rfile.name):
|
145
|
+
try:
|
146
|
+
os.remove(rfile.name)
|
147
|
+
except OSError:
|
148
|
+
pass # sometimes we can't remove a generated temporary file
|
149
|
+
|
150
|
+
|
151
|
+
@pytest.fixture(scope='module', name='errorassumpfile')
|
152
|
+
def fixture_errorassumpfile():
|
153
|
+
"""
|
154
|
+
Temporary assumption file with .json extension.
|
155
|
+
"""
|
156
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
157
|
+
contents = """
|
158
|
+
{
|
159
|
+
"consumption": {"MPC_e18400": {"2018": -9}},
|
160
|
+
"growdiff_baseline": {"ABOOKxx": {"2017": 0.02}},
|
161
|
+
"growdiff_response": {"ABOOKxx": {"2017": 0.02}}
|
162
|
+
}
|
163
|
+
"""
|
164
|
+
rfile.write(contents)
|
165
|
+
rfile.close()
|
166
|
+
yield rfile
|
167
|
+
if os.path.isfile(rfile.name):
|
168
|
+
try:
|
169
|
+
os.remove(rfile.name)
|
170
|
+
except OSError:
|
171
|
+
pass # sometimes we can't remove a generated temporary file
|
172
|
+
|
173
|
+
|
174
|
+
@pytest.fixture(scope='module', name='assumpfile1')
|
175
|
+
def fixture_assumpfile1():
|
176
|
+
"""
|
177
|
+
Temporary assumption file with .json extension.
|
178
|
+
"""
|
179
|
+
afile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
180
|
+
contents = """
|
181
|
+
{
|
182
|
+
"consumption": { "MPC_e18400": {"2018": 0.05} },
|
183
|
+
"growdiff_baseline": {},
|
184
|
+
"growdiff_response": {}
|
185
|
+
}
|
186
|
+
"""
|
187
|
+
afile.write(contents)
|
188
|
+
afile.close()
|
189
|
+
yield afile
|
190
|
+
if os.path.isfile(afile.name):
|
191
|
+
try:
|
192
|
+
os.remove(afile.name)
|
193
|
+
except OSError:
|
194
|
+
pass # sometimes we can't remove a generated temporary file
|
195
|
+
|
196
|
+
|
197
|
+
@pytest.fixture(scope='module', name='lumpsumreformfile')
|
198
|
+
def fixture_lumpsumreformfile():
|
199
|
+
"""
|
200
|
+
Temporary reform file without .json extension.
|
201
|
+
"""
|
202
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
203
|
+
lumpsum_reform_contents = '{"policy": {"LST": {"2013": 200}}}'
|
204
|
+
rfile.write(lumpsum_reform_contents)
|
205
|
+
rfile.close()
|
206
|
+
yield rfile
|
207
|
+
if os.path.isfile(rfile.name):
|
208
|
+
try:
|
209
|
+
os.remove(rfile.name)
|
210
|
+
except OSError:
|
211
|
+
pass # sometimes we can't remove a generated temporary file
|
212
|
+
|
213
|
+
|
214
|
+
@pytest.fixture(scope='module', name='assumpfile2')
|
215
|
+
def fixture_assumpfile2():
|
216
|
+
"""
|
217
|
+
Temporary assumption file with .json extension.
|
218
|
+
"""
|
219
|
+
afile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
220
|
+
assump2_contents = """
|
221
|
+
{
|
222
|
+
"consumption": {"BEN_snap_value": {"2018": 0.90}},
|
223
|
+
"growdiff_baseline": {},
|
224
|
+
"growdiff_response": {}
|
225
|
+
}
|
226
|
+
"""
|
227
|
+
afile.write(assump2_contents)
|
228
|
+
afile.close()
|
229
|
+
yield afile
|
230
|
+
if os.path.isfile(afile.name):
|
231
|
+
try:
|
232
|
+
os.remove(afile.name)
|
233
|
+
except OSError:
|
234
|
+
pass # sometimes we can't remove a generated temporary file
|
235
|
+
|
236
|
+
|
237
|
+
@pytest.mark.parametrize('input_data, baseline, reform, assump, outdir', [
|
238
|
+
('no-dot-csv-filename', 'no-dot-json-filename',
|
239
|
+
'no-dot-json-filename',
|
240
|
+
'no-dot-json-filename', 'no-output-directory'),
|
241
|
+
(list(), list(), list(), list(), list()),
|
242
|
+
('no-exist.csv', 'no-exist.json', 'no-exist.json', 'no-exist.json', '.'),
|
243
|
+
])
|
244
|
+
def test_ctor_errors(input_data, baseline, reform, assump, outdir):
|
245
|
+
"""
|
246
|
+
Ensure error messages are generated by TaxCalcIO.__init__.
|
247
|
+
"""
|
248
|
+
tcio = TaxCalcIO(input_data=input_data, tax_year=2013,
|
249
|
+
baseline=baseline, reform=reform, assump=assump,
|
250
|
+
outdir=outdir)
|
251
|
+
assert tcio.errmsg
|
252
|
+
|
253
|
+
|
254
|
+
@pytest.mark.parametrize('year, base, ref, asm', [
|
255
|
+
(2000, 'reformfile0', 'reformfile0', None),
|
256
|
+
(2099, 'reformfile0', 'reformfile0', None),
|
257
|
+
(2020, 'reformfile0', 'reformfile0', 'errorassumpfile'),
|
258
|
+
(2020, 'errorreformfile', 'errorreformfile', None)
|
259
|
+
])
|
260
|
+
def test_init_errors(reformfile0, errorreformfile, errorassumpfile,
|
261
|
+
year, base, ref, asm):
|
262
|
+
"""
|
263
|
+
Ensure error messages generated correctly by TaxCalcIO.init method.
|
264
|
+
"""
|
265
|
+
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
|
266
|
+
recdict = {'RECID': 1, 'MARS': 1, 'e00300': 100000, 's006': 1e8}
|
267
|
+
recdf = pd.DataFrame(data=recdict, index=[0])
|
268
|
+
# test TaxCalcIO ctor
|
269
|
+
if base == 'reformfile0':
|
270
|
+
baseline = reformfile0.name
|
271
|
+
elif base == 'errorreformfile':
|
272
|
+
baseline = errorreformfile.name
|
273
|
+
else:
|
274
|
+
baseline = base
|
275
|
+
if ref == 'reformfile0':
|
276
|
+
reform = reformfile0.name
|
277
|
+
elif ref == 'errorreformfile':
|
278
|
+
reform = errorreformfile.name
|
279
|
+
else:
|
280
|
+
reform = ref
|
281
|
+
if asm == 'errorassumpfile':
|
282
|
+
assump = errorassumpfile.name
|
283
|
+
else:
|
284
|
+
assump = asm
|
285
|
+
# call TaxCalcIO constructor
|
286
|
+
tcio = TaxCalcIO(input_data=recdf,
|
287
|
+
tax_year=year,
|
288
|
+
baseline=baseline,
|
289
|
+
reform=reform,
|
290
|
+
assump=assump)
|
291
|
+
assert not tcio.errmsg
|
292
|
+
# test TaxCalcIO.init method
|
293
|
+
tcio.init(input_data=recdf, tax_year=year,
|
294
|
+
baseline=baseline, reform=reform, assump=assump,
|
295
|
+
aging_input_data=False,
|
296
|
+
exact_calculations=True)
|
297
|
+
assert tcio.errmsg
|
298
|
+
|
299
|
+
|
300
|
+
def test_creation_with_aging(reformfile0):
|
301
|
+
"""
|
302
|
+
Test TaxCalcIO instantiation with/without no policy reform and with aging.
|
303
|
+
"""
|
304
|
+
taxyear = 2021
|
305
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
306
|
+
tax_year=taxyear,
|
307
|
+
baseline=None,
|
308
|
+
reform=reformfile0.name,
|
309
|
+
assump=None)
|
310
|
+
assert not tcio.errmsg
|
311
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
312
|
+
tax_year=taxyear,
|
313
|
+
baseline=None,
|
314
|
+
reform=reformfile0.name,
|
315
|
+
assump=None,
|
316
|
+
aging_input_data=True,
|
317
|
+
exact_calculations=False)
|
318
|
+
assert not tcio.errmsg
|
319
|
+
assert tcio.tax_year() == taxyear
|
320
|
+
taxyear = 2016
|
321
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
322
|
+
tax_year=taxyear,
|
323
|
+
baseline=None,
|
324
|
+
reform=None,
|
325
|
+
assump=None)
|
326
|
+
assert not tcio.errmsg
|
327
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
328
|
+
tax_year=taxyear,
|
329
|
+
baseline=None,
|
330
|
+
reform=None,
|
331
|
+
assump=None,
|
332
|
+
aging_input_data=True,
|
333
|
+
exact_calculations=False)
|
334
|
+
assert not tcio.errmsg
|
335
|
+
assert tcio.tax_year() == taxyear
|
336
|
+
|
337
|
+
|
338
|
+
def test_ctor_init_with_cps_files():
|
339
|
+
"""
|
340
|
+
Test use of CPS input files.
|
341
|
+
"""
|
342
|
+
# specify valid tax_year for cps.csv input data
|
343
|
+
txyr = 2020
|
344
|
+
tcio = TaxCalcIO('cps.csv', txyr, None, None, None)
|
345
|
+
tcio.init('cps.csv', txyr, None, None, None,
|
346
|
+
aging_input_data=True,
|
347
|
+
exact_calculations=False)
|
348
|
+
assert not tcio.errmsg
|
349
|
+
assert tcio.tax_year() == txyr
|
350
|
+
# specify invalid tax_year for cps.csv input data
|
351
|
+
txyr = 2013
|
352
|
+
tcio = TaxCalcIO('cps.csv', txyr, None, None, None)
|
353
|
+
tcio.init('cps.csv', txyr, None, None, None,
|
354
|
+
aging_input_data=True,
|
355
|
+
exact_calculations=False)
|
356
|
+
assert tcio.errmsg
|
357
|
+
|
358
|
+
|
359
|
+
@pytest.mark.parametrize("dumpvar_str, str_valid, num_vars", [
|
360
|
+
("""
|
361
|
+
MARS;iitax payrolltax|combined,
|
362
|
+
c00100
|
363
|
+
surtax
|
364
|
+
""", True, 8), # these 6 parameters plus added RECID and FLPDYR
|
365
|
+
|
366
|
+
("""
|
367
|
+
MARS;iitax payrolltax|kombined,c00100
|
368
|
+
surtax
|
369
|
+
RECID
|
370
|
+
FLPDYR
|
371
|
+
""", False, 8)
|
372
|
+
])
|
373
|
+
def test_custom_dump_variables(dumpvar_str, str_valid, num_vars):
|
374
|
+
"""
|
375
|
+
Test TaxCalcIO custom_dump_variables method.
|
376
|
+
"""
|
377
|
+
recdict = {'RECID': 1, 'MARS': 1, 'e00300': 100000, 's006': 1e8}
|
378
|
+
recdf = pd.DataFrame(data=recdict, index=[0])
|
379
|
+
year = 2018
|
380
|
+
tcio = TaxCalcIO(input_data=recdf, tax_year=year,
|
381
|
+
baseline=None, reform=None, assump=None)
|
382
|
+
assert not tcio.errmsg
|
383
|
+
tcio.init(input_data=recdf, tax_year=year,
|
384
|
+
baseline=None, reform=None, assump=None,
|
385
|
+
aging_input_data=False,
|
386
|
+
exact_calculations=False)
|
387
|
+
assert not tcio.errmsg
|
388
|
+
varset = tcio.custom_dump_variables(dumpvar_str)
|
389
|
+
assert isinstance(varset, set)
|
390
|
+
valid = len(tcio.errmsg) == 0
|
391
|
+
assert valid == str_valid
|
392
|
+
if valid:
|
393
|
+
assert len(varset) == num_vars
|
394
|
+
|
395
|
+
|
396
|
+
def test_output_options(reformfile1, assumpfile1):
|
397
|
+
"""
|
398
|
+
Test TaxCalcIO output_dump options when writing_output_file.
|
399
|
+
"""
|
400
|
+
taxyear = 2021
|
401
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
402
|
+
tax_year=taxyear,
|
403
|
+
baseline=None,
|
404
|
+
reform=reformfile1.name,
|
405
|
+
assump=assumpfile1.name)
|
406
|
+
assert not tcio.errmsg
|
407
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
408
|
+
tax_year=taxyear,
|
409
|
+
baseline=None,
|
410
|
+
reform=reformfile1.name,
|
411
|
+
assump=assumpfile1.name,
|
412
|
+
aging_input_data=False,
|
413
|
+
exact_calculations=False)
|
414
|
+
assert not tcio.errmsg
|
415
|
+
outfilepath = tcio.output_filepath()
|
416
|
+
# minimal output with no --dump option
|
417
|
+
try:
|
418
|
+
tcio.analyze(writing_output_file=True, output_dump=False)
|
419
|
+
except Exception: # pylint: disable=broad-except
|
420
|
+
if os.path.isfile(outfilepath):
|
421
|
+
try:
|
422
|
+
os.remove(outfilepath)
|
423
|
+
except OSError:
|
424
|
+
pass # sometimes we can't remove a generated temporary file
|
425
|
+
assert 'TaxCalcIO.analyze(minimal_output)_ok' == 'no'
|
426
|
+
# --dump output with full dump
|
427
|
+
try:
|
428
|
+
tcio.analyze(writing_output_file=True, output_dump=True)
|
429
|
+
except Exception: # pylint: disable=broad-except
|
430
|
+
if os.path.isfile(outfilepath):
|
431
|
+
try:
|
432
|
+
os.remove(outfilepath)
|
433
|
+
except OSError:
|
434
|
+
pass # sometimes we can't remove a generated temporary file
|
435
|
+
assert 'TaxCalcIO.analyze(full_dump_output)_ok' == 'no'
|
436
|
+
# --dump output with partial dump
|
437
|
+
try:
|
438
|
+
tcio.analyze(writing_output_file=True,
|
439
|
+
dump_varset=set(['RECID', 'combined']),
|
440
|
+
output_dump=True)
|
441
|
+
except Exception: # pylint: disable=broad-except
|
442
|
+
if os.path.isfile(outfilepath):
|
443
|
+
try:
|
444
|
+
os.remove(outfilepath)
|
445
|
+
except OSError:
|
446
|
+
pass # sometimes we can't remove a generated temporary file
|
447
|
+
assert 'TaxCalcIO.analyze(partial_dump_output)_ok' == 'no'
|
448
|
+
# if tries were successful, remove doc file and output file
|
449
|
+
docfilepath = outfilepath.replace('.csv', '-doc.text')
|
450
|
+
if os.path.isfile(docfilepath):
|
451
|
+
os.remove(docfilepath)
|
452
|
+
if os.path.isfile(outfilepath):
|
453
|
+
os.remove(outfilepath)
|
454
|
+
|
455
|
+
|
456
|
+
def test_write_doc_file(reformfile1, assumpfile1):
|
457
|
+
"""
|
458
|
+
Test write_doc_file with compound reform.
|
459
|
+
"""
|
460
|
+
taxyear = 2021
|
461
|
+
compound_reform = '{}+{}'.format(reformfile1.name, reformfile1.name)
|
462
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
463
|
+
tax_year=taxyear,
|
464
|
+
baseline=None,
|
465
|
+
reform=compound_reform,
|
466
|
+
assump=assumpfile1.name)
|
467
|
+
assert not tcio.errmsg
|
468
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
469
|
+
tax_year=taxyear,
|
470
|
+
baseline=None,
|
471
|
+
reform=compound_reform,
|
472
|
+
assump=assumpfile1.name,
|
473
|
+
aging_input_data=False,
|
474
|
+
exact_calculations=False)
|
475
|
+
assert not tcio.errmsg
|
476
|
+
tcio.write_doc_file()
|
477
|
+
outfilepath = tcio.output_filepath()
|
478
|
+
docfilepath = outfilepath.replace('.csv', '-doc.text')
|
479
|
+
if os.path.isfile(docfilepath):
|
480
|
+
os.remove(docfilepath)
|
481
|
+
|
482
|
+
|
483
|
+
def test_sqldb_option(reformfile1, assumpfile1):
|
484
|
+
"""
|
485
|
+
Test TaxCalcIO output_sqldb option when not writing_output_file.
|
486
|
+
"""
|
487
|
+
taxyear = 2021
|
488
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
489
|
+
tax_year=taxyear,
|
490
|
+
baseline=None,
|
491
|
+
reform=reformfile1.name,
|
492
|
+
assump=assumpfile1.name)
|
493
|
+
assert not tcio.errmsg
|
494
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
495
|
+
tax_year=taxyear,
|
496
|
+
baseline=None,
|
497
|
+
reform=reformfile1.name,
|
498
|
+
assump=assumpfile1.name,
|
499
|
+
aging_input_data=False,
|
500
|
+
exact_calculations=False)
|
501
|
+
assert not tcio.errmsg
|
502
|
+
outfilepath = tcio.output_filepath()
|
503
|
+
dbfilepath = outfilepath.replace('.csv', '.db')
|
504
|
+
# --sqldb output
|
505
|
+
try:
|
506
|
+
tcio.analyze(writing_output_file=False, output_sqldb=True)
|
507
|
+
except Exception: # pylint: disable=broad-except
|
508
|
+
if os.path.isfile(dbfilepath):
|
509
|
+
try:
|
510
|
+
os.remove(dbfilepath)
|
511
|
+
except OSError:
|
512
|
+
pass # sometimes we can't remove a generated temporary file
|
513
|
+
assert 'TaxCalcIO.analyze(sqldb)_ok' == 'no'
|
514
|
+
# if try was successful, remove the db file
|
515
|
+
if os.path.isfile(dbfilepath):
|
516
|
+
os.remove(dbfilepath)
|
517
|
+
|
518
|
+
|
519
|
+
def test_no_tables_or_graphs(reformfile1):
|
520
|
+
"""
|
521
|
+
Test TaxCalcIO with output_tables=True and output_graphs=True but
|
522
|
+
INPUT has zero weights.
|
523
|
+
"""
|
524
|
+
# create input sample that cannot output tables or graphs
|
525
|
+
nobs = 10
|
526
|
+
idict = dict()
|
527
|
+
idict['RECID'] = [i for i in range(1, nobs + 1)]
|
528
|
+
idict['MARS'] = [2 for i in range(1, nobs + 1)]
|
529
|
+
idict['s006'] = [0.0 for i in range(1, nobs + 1)]
|
530
|
+
idict['e00300'] = [10000 * i for i in range(1, nobs + 1)]
|
531
|
+
idict['expanded_income'] = idict['e00300']
|
532
|
+
idf = pd.DataFrame(idict, columns=list(idict))
|
533
|
+
# create and initialize TaxCalcIO object
|
534
|
+
tcio = TaxCalcIO(input_data=idf,
|
535
|
+
tax_year=2020,
|
536
|
+
baseline=None,
|
537
|
+
reform=reformfile1.name,
|
538
|
+
assump=None)
|
539
|
+
assert not tcio.errmsg
|
540
|
+
tcio.init(input_data=idf,
|
541
|
+
tax_year=2020,
|
542
|
+
baseline=None,
|
543
|
+
reform=reformfile1.name,
|
544
|
+
assump=None,
|
545
|
+
aging_input_data=False,
|
546
|
+
exact_calculations=False)
|
547
|
+
assert not tcio.errmsg
|
548
|
+
# create TaxCalcIO tables file
|
549
|
+
tcio.analyze(writing_output_file=False,
|
550
|
+
output_tables=True,
|
551
|
+
output_graphs=True)
|
552
|
+
# delete tables and graph files
|
553
|
+
output_filename = tcio.output_filepath()
|
554
|
+
fname = output_filename.replace('.csv', '-tab.text')
|
555
|
+
if os.path.isfile(fname):
|
556
|
+
os.remove(fname)
|
557
|
+
fname = output_filename.replace('.csv', '-atr.html')
|
558
|
+
if os.path.isfile(fname):
|
559
|
+
os.remove(fname)
|
560
|
+
fname = output_filename.replace('.csv', '-mtr.html')
|
561
|
+
if os.path.isfile(fname):
|
562
|
+
os.remove(fname)
|
563
|
+
fname = output_filename.replace('.csv', '-pch.html')
|
564
|
+
if os.path.isfile(fname):
|
565
|
+
os.remove(fname)
|
566
|
+
|
567
|
+
|
568
|
+
def test_tables(reformfile1):
|
569
|
+
"""
|
570
|
+
Test TaxCalcIO with output_tables=True and with positive weights.
|
571
|
+
"""
|
572
|
+
# create tabable input
|
573
|
+
nobs = 100
|
574
|
+
idict = dict()
|
575
|
+
idict['RECID'] = [i for i in range(1, nobs + 1)]
|
576
|
+
idict['MARS'] = [2 for i in range(1, nobs + 1)]
|
577
|
+
idict['s006'] = [10.0 for i in range(1, nobs + 1)]
|
578
|
+
idict['e00300'] = [10000 * i for i in range(1, nobs + 1)]
|
579
|
+
idict['expanded_income'] = idict['e00300']
|
580
|
+
idf = pd.DataFrame(idict, columns=list(idict))
|
581
|
+
# create and initialize TaxCalcIO object
|
582
|
+
tcio = TaxCalcIO(input_data=idf,
|
583
|
+
tax_year=2020,
|
584
|
+
baseline=None,
|
585
|
+
reform=reformfile1.name,
|
586
|
+
assump=None)
|
587
|
+
assert not tcio.errmsg
|
588
|
+
tcio.init(input_data=idf,
|
589
|
+
tax_year=2020,
|
590
|
+
baseline=None,
|
591
|
+
reform=reformfile1.name,
|
592
|
+
assump=None,
|
593
|
+
aging_input_data=False,
|
594
|
+
exact_calculations=False)
|
595
|
+
assert not tcio.errmsg
|
596
|
+
# create TaxCalcIO tables file
|
597
|
+
tcio.analyze(writing_output_file=False, output_tables=True)
|
598
|
+
# delete tables file
|
599
|
+
output_filename = tcio.output_filepath()
|
600
|
+
fname = output_filename.replace('.csv', '-tab.text')
|
601
|
+
if os.path.isfile(fname):
|
602
|
+
os.remove(fname)
|
603
|
+
|
604
|
+
|
605
|
+
def test_graphs(reformfile1):
|
606
|
+
"""
|
607
|
+
Test TaxCalcIO with output_graphs=True.
|
608
|
+
"""
|
609
|
+
# create graphable input
|
610
|
+
nobs = 100
|
611
|
+
idict = dict()
|
612
|
+
idict['RECID'] = [i for i in range(1, nobs + 1)]
|
613
|
+
idict['MARS'] = [2 for i in range(1, nobs + 1)]
|
614
|
+
idict['XTOT'] = [3 for i in range(1, nobs + 1)]
|
615
|
+
idict['s006'] = [10.0 for i in range(1, nobs + 1)]
|
616
|
+
idict['e00300'] = [10000 * i for i in range(1, nobs + 1)]
|
617
|
+
idict['expanded_income'] = idict['e00300']
|
618
|
+
idf = pd.DataFrame(idict, columns=list(idict))
|
619
|
+
# create and initialize TaxCalcIO object
|
620
|
+
tcio = TaxCalcIO(input_data=idf,
|
621
|
+
tax_year=2020,
|
622
|
+
baseline=None,
|
623
|
+
reform=reformfile1.name,
|
624
|
+
assump=None)
|
625
|
+
assert not tcio.errmsg
|
626
|
+
tcio.init(input_data=idf,
|
627
|
+
tax_year=2020,
|
628
|
+
baseline=None,
|
629
|
+
reform=reformfile1.name,
|
630
|
+
assump=None,
|
631
|
+
aging_input_data=False,
|
632
|
+
exact_calculations=False)
|
633
|
+
assert not tcio.errmsg
|
634
|
+
tcio.analyze(writing_output_file=False, output_graphs=True)
|
635
|
+
# delete graph files
|
636
|
+
output_filename = tcio.output_filepath()
|
637
|
+
fname = output_filename.replace('.csv', '-atr.html')
|
638
|
+
if os.path.isfile(fname):
|
639
|
+
os.remove(fname)
|
640
|
+
fname = output_filename.replace('.csv', '-mtr.html')
|
641
|
+
if os.path.isfile(fname):
|
642
|
+
os.remove(fname)
|
643
|
+
fname = output_filename.replace('.csv', '-pch.html')
|
644
|
+
if os.path.isfile(fname):
|
645
|
+
os.remove(fname)
|
646
|
+
|
647
|
+
|
648
|
+
@pytest.fixture(scope='module', name='warnreformfile')
|
649
|
+
def fixture_warnreformfile():
|
650
|
+
"""
|
651
|
+
Temporary reform file with .json extension.
|
652
|
+
"""
|
653
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
654
|
+
contents = '{"policy": {"STD_Dep": {"2015": 0}}}'
|
655
|
+
rfile.write(contents)
|
656
|
+
rfile.close()
|
657
|
+
yield rfile
|
658
|
+
if os.path.isfile(rfile.name):
|
659
|
+
try:
|
660
|
+
os.remove(rfile.name)
|
661
|
+
except OSError:
|
662
|
+
pass # sometimes we can't remove a generated temporary file
|
663
|
+
|
664
|
+
|
665
|
+
def test_analyze_warnings_print(warnreformfile):
|
666
|
+
"""
|
667
|
+
Test TaxCalcIO.analyze method when there is a reform warning.
|
668
|
+
"""
|
669
|
+
taxyear = 2020
|
670
|
+
recdict = {'RECID': 1, 'MARS': 1, 'e00300': 100000, 's006': 1e8}
|
671
|
+
recdf = pd.DataFrame(data=recdict, index=[0])
|
672
|
+
tcio = TaxCalcIO(input_data=recdf,
|
673
|
+
tax_year=taxyear,
|
674
|
+
baseline=None,
|
675
|
+
reform=warnreformfile.name,
|
676
|
+
assump=None)
|
677
|
+
assert not tcio.errmsg
|
678
|
+
tcio.init(input_data=recdf,
|
679
|
+
tax_year=taxyear,
|
680
|
+
baseline=None,
|
681
|
+
reform=warnreformfile.name,
|
682
|
+
assump=None,
|
683
|
+
aging_input_data=False,
|
684
|
+
exact_calculations=False)
|
685
|
+
assert not tcio.errmsg
|
686
|
+
tcio.analyze(writing_output_file=False)
|
687
|
+
assert tcio.tax_year() == taxyear
|
688
|
+
|
689
|
+
|
690
|
+
@pytest.fixture(scope='module', name='reformfile9')
|
691
|
+
def fixture_reformfile9():
|
692
|
+
"""
|
693
|
+
Temporary reform file with .json extension.
|
694
|
+
"""
|
695
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
696
|
+
contents = """
|
697
|
+
{ "policy": {
|
698
|
+
"SS_Earnings_c": {
|
699
|
+
"2014": 300000,
|
700
|
+
"2015": 500000,
|
701
|
+
"2016": 700000}
|
702
|
+
}
|
703
|
+
}
|
704
|
+
"""
|
705
|
+
rfile.write(contents)
|
706
|
+
rfile.close()
|
707
|
+
yield rfile
|
708
|
+
if os.path.isfile(rfile.name):
|
709
|
+
try:
|
710
|
+
os.remove(rfile.name)
|
711
|
+
except OSError:
|
712
|
+
pass # sometimes we can't remove a generated temporary file
|
713
|
+
|
714
|
+
|
715
|
+
@pytest.fixture(scope='module', name='regression_reform_file')
|
716
|
+
def fixture_regression_reform_file():
|
717
|
+
"""
|
718
|
+
Temporary reform file with .json extension.
|
719
|
+
|
720
|
+
Example causing regression reported in issue:
|
721
|
+
https://github.com/PSLmodels/Tax-Calculator/issues/2622
|
722
|
+
"""
|
723
|
+
rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False)
|
724
|
+
contents = '{ "policy": {"AMEDT_rt": {"2021": 1.8}}}'
|
725
|
+
rfile.write(contents)
|
726
|
+
rfile.close()
|
727
|
+
yield rfile
|
728
|
+
if os.path.isfile(rfile.name):
|
729
|
+
try:
|
730
|
+
os.remove(rfile.name)
|
731
|
+
except OSError:
|
732
|
+
pass # sometimes we can't remove a generated temporary file
|
733
|
+
|
734
|
+
|
735
|
+
def test_error_message_parsed_correctly(regression_reform_file):
|
736
|
+
tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
737
|
+
tax_year=2022,
|
738
|
+
baseline=regression_reform_file.name,
|
739
|
+
reform=regression_reform_file.name,
|
740
|
+
assump=None)
|
741
|
+
assert not tcio.errmsg
|
742
|
+
|
743
|
+
tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
|
744
|
+
tax_year=2022,
|
745
|
+
baseline=regression_reform_file.name,
|
746
|
+
reform=regression_reform_file.name,
|
747
|
+
assump=None,
|
748
|
+
aging_input_data=False,
|
749
|
+
exact_calculations=False)
|
750
|
+
assert isinstance(tcio.errmsg, str) and tcio.errmsg
|
751
|
+
exp_errmsg = (
|
752
|
+
"AMEDT_rt[year=2021] 1.8 > max 1 \n"
|
753
|
+
"AMEDT_rt[year=2021] 1.8 > max 1 "
|
754
|
+
)
|
755
|
+
assert tcio.errmsg == exp_errmsg
|