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.
Files changed (123) hide show
  1. taxcalc/__init__.py +1 -1
  2. taxcalc/assumptions/ASSUMPTIONS.md +53 -0
  3. taxcalc/assumptions/README.md +17 -0
  4. taxcalc/assumptions/economic_assumptions_template.json +77 -0
  5. taxcalc/calcfunctions.py +7 -4
  6. taxcalc/data.py +10 -5
  7. taxcalc/policy_current_law.json +2033 -184
  8. taxcalc/reforms/2017_law.json +125 -0
  9. taxcalc/reforms/2017_law.out.csv +10 -0
  10. taxcalc/reforms/ARPA.json +78 -0
  11. taxcalc/reforms/ARPA.out.csv +10 -0
  12. taxcalc/reforms/BrownKhanna.json +23 -0
  13. taxcalc/reforms/BrownKhanna.out.csv +10 -0
  14. taxcalc/reforms/CARES.json +40 -0
  15. taxcalc/reforms/CARES.out.csv +10 -0
  16. taxcalc/reforms/ConsolidatedAppropriationsAct2021.json +15 -0
  17. taxcalc/reforms/ConsolidatedAppropriationsAct2021.out.csv +10 -0
  18. taxcalc/reforms/Larson2019.json +36 -0
  19. taxcalc/reforms/Larson2019.out.csv +10 -0
  20. taxcalc/reforms/README.md +22 -0
  21. taxcalc/reforms/REFORMS.md +92 -0
  22. taxcalc/reforms/Renacci.json +61 -0
  23. taxcalc/reforms/Renacci.out.csv +10 -0
  24. taxcalc/reforms/SandersDeFazio.json +15 -0
  25. taxcalc/reforms/SandersDeFazio.out.csv +10 -0
  26. taxcalc/reforms/TCJA.json +160 -0
  27. taxcalc/reforms/TCJA.md +48 -0
  28. taxcalc/reforms/TCJA.out.csv +10 -0
  29. taxcalc/reforms/Trump2016.json +71 -0
  30. taxcalc/reforms/Trump2016.out.csv +10 -0
  31. taxcalc/reforms/Trump2017.json +51 -0
  32. taxcalc/reforms/Trump2017.out.csv +10 -0
  33. taxcalc/reforms/archive/Clinton2016.json +56 -0
  34. taxcalc/reforms/archive/RyanBrady.json +104 -0
  35. taxcalc/reforms/archive/TCJA_House.json +144 -0
  36. taxcalc/reforms/archive/TCJA_House_Amended.json +152 -0
  37. taxcalc/reforms/archive/TCJA_Reconciliation.json +187 -0
  38. taxcalc/reforms/archive/TCJA_Senate.json +116 -0
  39. taxcalc/reforms/archive/TCJA_Senate_111417.json +169 -0
  40. taxcalc/reforms/archive/TCJA_Senate_120117.json +174 -0
  41. taxcalc/reforms/cases.csv +10 -0
  42. taxcalc/reforms/clp.out.csv +10 -0
  43. taxcalc/reforms/ext.json +59 -0
  44. taxcalc/reforms/growfactors_ext.csv +65 -0
  45. taxcalc/reforms/ptaxes0.json +37 -0
  46. taxcalc/reforms/ptaxes0.out.csv +10 -0
  47. taxcalc/reforms/ptaxes1.json +21 -0
  48. taxcalc/reforms/ptaxes1.out.csv +10 -0
  49. taxcalc/reforms/ptaxes2.json +18 -0
  50. taxcalc/reforms/ptaxes2.out.csv +10 -0
  51. taxcalc/reforms/ptaxes3.json +28 -0
  52. taxcalc/reforms/ptaxes3.out.csv +10 -0
  53. taxcalc/reforms/rounding2022.json +153 -0
  54. taxcalc/reforms/rounding2022.out.csv +10 -0
  55. taxcalc/tests/benefits_expect.csv +169 -0
  56. taxcalc/tests/cmpi_cps_expect.txt +132 -0
  57. taxcalc/tests/cmpi_puf_expect.txt +132 -0
  58. taxcalc/tests/conftest.py +143 -0
  59. taxcalc/tests/cpscsv_agg_expect.csv +26 -0
  60. taxcalc/tests/puf_var_correl_coeffs_2016.csv +80 -0
  61. taxcalc/tests/puf_var_wght_means_by_year.csv +80 -0
  62. taxcalc/tests/pufcsv_agg_expect.csv +26 -0
  63. taxcalc/tests/pufcsv_mtr_expect.txt +63 -0
  64. taxcalc/tests/reforms.json +649 -0
  65. taxcalc/tests/reforms_expect.csv +65 -0
  66. taxcalc/tests/test_4package.py +67 -0
  67. taxcalc/tests/test_benefits.py +86 -0
  68. taxcalc/tests/test_calcfunctions.py +871 -0
  69. taxcalc/tests/test_calculator.py +1021 -0
  70. taxcalc/tests/test_compare.py +336 -0
  71. taxcalc/tests/test_compatible_data.py +338 -0
  72. taxcalc/tests/test_consumption.py +144 -0
  73. taxcalc/tests/test_cpscsv.py +163 -0
  74. taxcalc/tests/test_data.py +133 -0
  75. taxcalc/tests/test_decorators.py +332 -0
  76. taxcalc/tests/test_growdiff.py +102 -0
  77. taxcalc/tests/test_growfactors.py +94 -0
  78. taxcalc/tests/test_parameters.py +617 -0
  79. taxcalc/tests/test_policy.py +1575 -0
  80. taxcalc/tests/test_puf_var_stats.py +194 -0
  81. taxcalc/tests/test_pufcsv.py +385 -0
  82. taxcalc/tests/test_records.py +234 -0
  83. taxcalc/tests/test_reforms.py +385 -0
  84. taxcalc/tests/test_responses.py +41 -0
  85. taxcalc/tests/test_taxcalcio.py +755 -0
  86. taxcalc/tests/test_tmdcsv.py +38 -0
  87. taxcalc/tests/test_utils.py +792 -0
  88. taxcalc/tmd_growfactors.csv +54 -54
  89. taxcalc/tmd_weights.csv.gz +0 -0
  90. taxcalc/validation/CSV_INPUT_VARS.md +29 -0
  91. taxcalc/validation/CSV_OUTPUT_VARS.md +63 -0
  92. taxcalc/validation/README.md +68 -0
  93. taxcalc/validation/taxsim35/Differences_Explained.md +54 -0
  94. taxcalc/validation/taxsim35/README.md +139 -0
  95. taxcalc/validation/taxsim35/expected_differences/a17-taxdiffs-expect.csv +25 -0
  96. taxcalc/validation/taxsim35/expected_differences/a18-taxdiffs-expect.csv +25 -0
  97. taxcalc/validation/taxsim35/expected_differences/a19-taxdiffs-expect.csv +25 -0
  98. taxcalc/validation/taxsim35/expected_differences/a20-taxdiffs-expect.csv +25 -0
  99. taxcalc/validation/taxsim35/expected_differences/a21-taxdiffs-expect.csv +25 -0
  100. taxcalc/validation/taxsim35/expected_differences/b17-taxdiffs-expect.csv +25 -0
  101. taxcalc/validation/taxsim35/expected_differences/b18-taxdiffs-expect.csv +25 -0
  102. taxcalc/validation/taxsim35/expected_differences/b19-taxdiffs-expect.csv +25 -0
  103. taxcalc/validation/taxsim35/expected_differences/b20-taxdiffs-expect.csv +25 -0
  104. taxcalc/validation/taxsim35/expected_differences/b21-taxdiffs-expect.csv +25 -0
  105. taxcalc/validation/taxsim35/expected_differences/c17-taxdiffs-expect.csv +25 -0
  106. taxcalc/validation/taxsim35/expected_differences/c18-taxdiffs-expect.csv +25 -0
  107. taxcalc/validation/taxsim35/expected_differences/c19-taxdiffs-expect.csv +25 -0
  108. taxcalc/validation/taxsim35/input_setup.py +67 -0
  109. taxcalc/validation/taxsim35/main_comparison.py +183 -0
  110. taxcalc/validation/taxsim35/prepare_taxcalc_input.py +161 -0
  111. taxcalc/validation/taxsim35/process_taxcalc_output.py +140 -0
  112. taxcalc/validation/taxsim35/taxsim_emulation.json +49 -0
  113. taxcalc/validation/taxsim35/taxsim_input.py +321 -0
  114. taxcalc/validation/taxsim35/tc_sims.py +98 -0
  115. taxcalc/validation/taxsim35/tests_35.py +80 -0
  116. taxcalc/validation/tests_35.sh +13 -0
  117. {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/METADATA +3 -4
  118. taxcalc-4.2.2.dist-info/RECORD +144 -0
  119. {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/WHEEL +1 -1
  120. taxcalc-4.2.1.dist-info/RECORD +0 -34
  121. {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/LICENSE +0 -0
  122. {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/entry_points.txt +0 -0
  123. {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