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