taxcalc 4.2.1__py3-none-any.whl → 4.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.py +1 -1
  8. taxcalc/policy_current_law.json +4649 -288
  9. taxcalc/records.py +20 -15
  10. taxcalc/reforms/2017_law.json +125 -0
  11. taxcalc/reforms/2017_law.out.csv +10 -0
  12. taxcalc/reforms/ARPA.json +78 -0
  13. taxcalc/reforms/ARPA.out.csv +10 -0
  14. taxcalc/reforms/BrownKhanna.json +23 -0
  15. taxcalc/reforms/BrownKhanna.out.csv +10 -0
  16. taxcalc/reforms/CARES.json +40 -0
  17. taxcalc/reforms/CARES.out.csv +10 -0
  18. taxcalc/reforms/ConsolidatedAppropriationsAct2021.json +15 -0
  19. taxcalc/reforms/ConsolidatedAppropriationsAct2021.out.csv +10 -0
  20. taxcalc/reforms/Larson2019.json +36 -0
  21. taxcalc/reforms/Larson2019.out.csv +10 -0
  22. taxcalc/reforms/README.md +22 -0
  23. taxcalc/reforms/REFORMS.md +92 -0
  24. taxcalc/reforms/Renacci.json +61 -0
  25. taxcalc/reforms/Renacci.out.csv +10 -0
  26. taxcalc/reforms/SandersDeFazio.json +15 -0
  27. taxcalc/reforms/SandersDeFazio.out.csv +10 -0
  28. taxcalc/reforms/TCJA.json +160 -0
  29. taxcalc/reforms/TCJA.md +48 -0
  30. taxcalc/reforms/TCJA.out.csv +10 -0
  31. taxcalc/reforms/Trump2016.json +71 -0
  32. taxcalc/reforms/Trump2016.out.csv +10 -0
  33. taxcalc/reforms/Trump2017.json +51 -0
  34. taxcalc/reforms/Trump2017.out.csv +10 -0
  35. taxcalc/reforms/archive/Clinton2016.json +56 -0
  36. taxcalc/reforms/archive/RyanBrady.json +104 -0
  37. taxcalc/reforms/archive/TCJA_House.json +144 -0
  38. taxcalc/reforms/archive/TCJA_House_Amended.json +152 -0
  39. taxcalc/reforms/archive/TCJA_Reconciliation.json +187 -0
  40. taxcalc/reforms/archive/TCJA_Senate.json +116 -0
  41. taxcalc/reforms/archive/TCJA_Senate_111417.json +169 -0
  42. taxcalc/reforms/archive/TCJA_Senate_120117.json +174 -0
  43. taxcalc/reforms/cases.csv +10 -0
  44. taxcalc/reforms/clp.out.csv +10 -0
  45. taxcalc/reforms/ext.json +59 -0
  46. taxcalc/reforms/growfactors_ext.csv +65 -0
  47. taxcalc/reforms/ptaxes0.json +37 -0
  48. taxcalc/reforms/ptaxes0.out.csv +10 -0
  49. taxcalc/reforms/ptaxes1.json +21 -0
  50. taxcalc/reforms/ptaxes1.out.csv +10 -0
  51. taxcalc/reforms/ptaxes2.json +18 -0
  52. taxcalc/reforms/ptaxes2.out.csv +10 -0
  53. taxcalc/reforms/ptaxes3.json +28 -0
  54. taxcalc/reforms/ptaxes3.out.csv +10 -0
  55. taxcalc/taxcalcio.py +44 -22
  56. taxcalc/tests/benefits_expect.csv +169 -0
  57. taxcalc/tests/cmpi_cps_expect.txt +132 -0
  58. taxcalc/tests/cmpi_puf_expect.txt +132 -0
  59. taxcalc/tests/conftest.py +143 -0
  60. taxcalc/tests/cpscsv_agg_expect.csv +26 -0
  61. taxcalc/tests/puf_var_correl_coeffs_2016.csv +80 -0
  62. taxcalc/tests/puf_var_wght_means_by_year.csv +80 -0
  63. taxcalc/tests/pufcsv_agg_expect.csv +26 -0
  64. taxcalc/tests/pufcsv_mtr_expect.txt +63 -0
  65. taxcalc/tests/reforms.json +649 -0
  66. taxcalc/tests/reforms_expect.csv +65 -0
  67. taxcalc/tests/test_4package.py +67 -0
  68. taxcalc/tests/test_benefits.py +86 -0
  69. taxcalc/tests/test_calcfunctions.py +871 -0
  70. taxcalc/tests/test_calculator.py +1021 -0
  71. taxcalc/tests/test_compare.py +336 -0
  72. taxcalc/tests/test_compatible_data.py +338 -0
  73. taxcalc/tests/test_consumption.py +144 -0
  74. taxcalc/tests/test_cpscsv.py +163 -0
  75. taxcalc/tests/test_data.py +133 -0
  76. taxcalc/tests/test_decorators.py +332 -0
  77. taxcalc/tests/test_growdiff.py +102 -0
  78. taxcalc/tests/test_growfactors.py +94 -0
  79. taxcalc/tests/test_parameters.py +617 -0
  80. taxcalc/tests/test_policy.py +1557 -0
  81. taxcalc/tests/test_puf_var_stats.py +194 -0
  82. taxcalc/tests/test_pufcsv.py +385 -0
  83. taxcalc/tests/test_records.py +234 -0
  84. taxcalc/tests/test_reforms.py +386 -0
  85. taxcalc/tests/test_responses.py +41 -0
  86. taxcalc/tests/test_taxcalcio.py +755 -0
  87. taxcalc/tests/test_utils.py +792 -0
  88. taxcalc/validation/CSV_INPUT_VARS.md +29 -0
  89. taxcalc/validation/CSV_OUTPUT_VARS.md +63 -0
  90. taxcalc/validation/README.md +68 -0
  91. taxcalc/validation/taxsim35/Differences_Explained.md +54 -0
  92. taxcalc/validation/taxsim35/README.md +139 -0
  93. taxcalc/validation/taxsim35/expected_differences/a17-taxdiffs-expect.csv +25 -0
  94. taxcalc/validation/taxsim35/expected_differences/a18-taxdiffs-expect.csv +25 -0
  95. taxcalc/validation/taxsim35/expected_differences/a19-taxdiffs-expect.csv +25 -0
  96. taxcalc/validation/taxsim35/expected_differences/a20-taxdiffs-expect.csv +25 -0
  97. taxcalc/validation/taxsim35/expected_differences/a21-taxdiffs-expect.csv +25 -0
  98. taxcalc/validation/taxsim35/expected_differences/b17-taxdiffs-expect.csv +25 -0
  99. taxcalc/validation/taxsim35/expected_differences/b18-taxdiffs-expect.csv +25 -0
  100. taxcalc/validation/taxsim35/expected_differences/b19-taxdiffs-expect.csv +25 -0
  101. taxcalc/validation/taxsim35/expected_differences/b20-taxdiffs-expect.csv +25 -0
  102. taxcalc/validation/taxsim35/expected_differences/b21-taxdiffs-expect.csv +25 -0
  103. taxcalc/validation/taxsim35/expected_differences/c17-taxdiffs-expect.csv +25 -0
  104. taxcalc/validation/taxsim35/expected_differences/c18-taxdiffs-expect.csv +25 -0
  105. taxcalc/validation/taxsim35/expected_differences/c19-taxdiffs-expect.csv +25 -0
  106. taxcalc/validation/taxsim35/input_setup.py +67 -0
  107. taxcalc/validation/taxsim35/main_comparison.py +183 -0
  108. taxcalc/validation/taxsim35/prepare_taxcalc_input.py +161 -0
  109. taxcalc/validation/taxsim35/process_taxcalc_output.py +140 -0
  110. taxcalc/validation/taxsim35/taxsim_emulation.json +49 -0
  111. taxcalc/validation/taxsim35/taxsim_input.py +321 -0
  112. taxcalc/validation/taxsim35/tc_sims.py +98 -0
  113. taxcalc/validation/taxsim35/tests_35.py +80 -0
  114. taxcalc/validation/tests_35.sh +13 -0
  115. {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/METADATA +3 -4
  116. taxcalc-4.3.0.dist-info/RECORD +139 -0
  117. {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/WHEEL +1 -1
  118. taxcalc/tmd_growfactors.csv +0 -55
  119. taxcalc/tmd_weights.csv.gz +0 -0
  120. taxcalc-4.2.1.dist-info/RECORD +0 -34
  121. {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/LICENSE +0 -0
  122. {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/entry_points.txt +0 -0
  123. {taxcalc-4.2.1.dist-info → taxcalc-4.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1557 @@
1
+ """
2
+ Test Policy class and its methods.
3
+ """
4
+ # CODING-STYLE CHECKS:
5
+ # pycodestyle test_policy.py
6
+ # pylint --disable=locally-disabled test_policy.py
7
+ #
8
+ # pylint: disable=too-many-lines
9
+
10
+ import copy
11
+ import os
12
+ import json
13
+ import numpy as np
14
+ import pytest
15
+ import paramtools as pt
16
+ # pylint: disable=import-error
17
+ from taxcalc import Policy
18
+
19
+
20
+ def cmp_policy_objs(pol1, pol2, year_range=None, exclude=None):
21
+ """
22
+ Compare parameter values two policy objects.
23
+
24
+ year_range: years over which to compare values.
25
+ exclude: list of parameters to exclude from comparison.
26
+ """
27
+ if year_range is not None:
28
+ pol1.set_state(year=list(year_range))
29
+ pol2.set_state(year=list(year_range))
30
+ else:
31
+ pol1.clear_state()
32
+ pol2.clear_state()
33
+ for param in pol1._data:
34
+ if exclude and param in exclude:
35
+ continue
36
+ v1 = getattr(pol1, param)
37
+ v2 = getattr(pol2, param)
38
+ np.testing.assert_allclose(v1, v2)
39
+
40
+
41
+ def test_incorrect_class_instantiation():
42
+ """
43
+ Test incorrect instantiation of Policy class object.
44
+ """
45
+ with pytest.raises(ValueError):
46
+ Policy(gfactors=[])
47
+
48
+
49
+ def test_correct_class_instantiation():
50
+ """
51
+ Test correct instantiation of Policy class object.
52
+ """
53
+ pol = Policy()
54
+ assert pol
55
+ pol.implement_reform({})
56
+ with pytest.raises(pt.ValidationError):
57
+ pol.implement_reform([])
58
+ with pytest.raises(pt.ValidationError):
59
+ pol.implement_reform({2099: {'II_em': 99000}})
60
+ pol.set_year(2019)
61
+ with pytest.raises(pt.ValidationError):
62
+ pol.implement_reform({2018: {'II_em': 99000}})
63
+ with pytest.raises(pt.ValidationError):
64
+ pol.implement_reform({2020: {'II_em': -1000}})
65
+
66
+
67
+ def test_json_reform_url():
68
+ """
69
+ Test reading a JSON reform from a URL. Results from the URL are expected
70
+ to match the results from the string.
71
+ """
72
+ reform_str = """
73
+ {
74
+ // raise FICA payroll tax rate in 2018 and 2020
75
+ "FICA_ss_trt_employer": {
76
+ "2018": 0.065,
77
+ "2020": 0.070
78
+ },
79
+ "FICA_ss_trt_employee": {
80
+ "2018": 0.065,
81
+ "2020": 0.070
82
+ },
83
+ // raise Medicare payroll tax rate in 2019 and 2021
84
+ "FICA_mc_trt_employer": {
85
+ "2019": 0.015,
86
+ "2021": 0.016
87
+ },
88
+ "FICA_mc_trt_employee": {
89
+ "2019": 0.015,
90
+ "2021": 0.016
91
+ }
92
+ }
93
+ """
94
+ reform_url = ('https://raw.githubusercontent.com/PSLmodels/'
95
+ 'Tax-Calculator/master/taxcalc/reforms/ptaxes0.json')
96
+ params_str = Policy.read_json_reform(reform_str)
97
+ params_url = Policy.read_json_reform(reform_url)
98
+ assert params_str == params_url
99
+
100
+ reform_gh_url = (
101
+ "github://PSLmodels:Tax-Calculator@master/taxcalc/reforms/ptaxes0.json"
102
+ )
103
+ params_gh_url = Policy.read_json_reform(reform_gh_url)
104
+ assert params_gh_url
105
+ assert params_gh_url == params_str
106
+
107
+
108
+ REFORM_JSON = """
109
+ // Example of a reform file suitable for Policy.read_json_reform().
110
+ // This JSON file can contain any number of trailing //-style comments, which
111
+ // will be removed before the contents are converted from JSON to a dictionary.
112
+ // The primary keys are parameters and the secondary keys are years.
113
+ // Both the primary and secondary key values must be enclosed in quotes (").
114
+ // Boolean variables are specified as true or false (no quotes; all lowercase).
115
+ {
116
+ "AMT_brk1": // top of first AMT tax bracket
117
+ {"2015": 200000,
118
+ "2017": 300000
119
+ },
120
+ "EITC_c": // maximum EITC amount by number of qualifying kids (0,1,2,3+)
121
+ {"2016": [ 900, 5000, 8000, 9000],
122
+ "2019": [1200, 7000, 10000, 12000]
123
+ },
124
+ "II_em": // personal exemption amount (see indexing changes below)
125
+ {"2016": 6000,
126
+ "2018": 7500,
127
+ "2020": 9000
128
+ },
129
+ "II_em-indexed": // personal exemption amount indexing status
130
+ {"2016": false, // values in future years are same as this year value
131
+ "2018": true // values in future years indexed with this year as base
132
+ },
133
+ "SS_Earnings_c": // social security (OASDI) maximum taxable earnings
134
+ {"2016": 300000,
135
+ "2018": 500000,
136
+ "2020": 700000
137
+ },
138
+ "AMT_em-indexed": // AMT exemption amount indexing status
139
+ {"2017": false, // values in future years are same as this year value
140
+ "2020": true // values in future years indexed with this year as base
141
+ }
142
+ }
143
+ """
144
+
145
+
146
+ # pylint: disable=protected-access,no-member
147
+
148
+
149
+ @pytest.mark.parametrize("set_year", [False, True])
150
+ def test_read_json_reform_file_and_implement_reform(set_year):
151
+ """
152
+ Test reading and translation of reform JSON into a reform dictionary
153
+ and then using that reform dictionary to implement reform.
154
+ """
155
+ pol = Policy()
156
+ if set_year:
157
+ pol.set_year(2015)
158
+ pol.implement_reform(Policy.read_json_reform(REFORM_JSON))
159
+ syr = pol.start_year
160
+ # pylint: disable=protected-access
161
+ amt_brk1 = pol._AMT_brk1
162
+ assert amt_brk1[2015 - syr] == 200000
163
+ assert amt_brk1[2016 - syr] > 200000
164
+ assert amt_brk1[2017 - syr] == 300000
165
+ assert amt_brk1[2018 - syr] > 300000
166
+ ii_em = pol._II_em
167
+ assert ii_em[2016 - syr] == 6000
168
+ assert ii_em[2017 - syr] == 6000
169
+ assert ii_em[2018 - syr] == 7500
170
+ assert ii_em[2019 - syr] > 7500
171
+ assert ii_em[2020 - syr] == 9000
172
+ assert ii_em[2021 - syr] > 9000
173
+ amt_em = pol._AMT_em
174
+ assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0]
175
+ assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0]
176
+ assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0]
177
+ assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0]
178
+ assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0]
179
+ assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0]
180
+ assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0]
181
+ add4aged = pol._ID_Medical_frt_add4aged
182
+ assert add4aged[2015 - syr] == -0.025
183
+ assert add4aged[2016 - syr] == -0.025
184
+ assert add4aged[2017 - syr] == 0.0
185
+ assert add4aged[2022 - syr] == 0.0
186
+
187
+
188
+ def test_constant_inflation_rate_with_reform():
189
+ """
190
+ Test indexing of policy parameters involved in a reform.
191
+ """
192
+ pol = Policy()
193
+ # implement reform in year before final year
194
+ fyr = 2034
195
+ ryr = fyr - 1
196
+ reform = {
197
+ 'II_em': {(ryr - 3): 1000, # to avoid divide-by-zero under TCJA
198
+ ryr: 20000}
199
+ }
200
+ pol.implement_reform(reform)
201
+ # extract price inflation rates
202
+ pirates = pol.inflation_rates()
203
+ syr = Policy.JSON_START_YEAR
204
+ irate_b = pirates[ryr - 2 - syr]
205
+ irate_a = pirates[ryr - syr]
206
+ # check implied inflation rate just before reform
207
+ grate = float(pol._II_em[ryr - 1 - syr]) / float(pol._II_em[ryr - 2 - syr])
208
+ assert round(grate - 1.0, 4) == round(irate_b, 4)
209
+ # check implied inflation rate just after reform
210
+ grate = float(pol._II_em[ryr + 1 - syr]) / float(pol._II_em[ryr - syr])
211
+ assert round(grate - 1.0, 6) == round(irate_a, 6)
212
+
213
+
214
+ def test_variable_inflation_rate_with_reform():
215
+ """
216
+ Test indexing of policy parameters involved in a reform.
217
+ """
218
+ pol = Policy()
219
+ syr = Policy.JSON_START_YEAR
220
+ assert pol._II_em[2013 - syr] == 3900
221
+ # implement reform in 2020 which is two years before the last year, 2022
222
+ reform = {
223
+ 'II_em': {2018: 1000, # to avoid divide-by-zero under TCJA
224
+ 2020: 20000}
225
+ }
226
+ pol.implement_reform(reform)
227
+ pol.set_year(2020)
228
+ assert pol.current_year == 2020
229
+ # extract price inflation rates
230
+ pirates = pol.inflation_rates()
231
+ irate2018 = pirates[2018 - syr]
232
+ irate2020 = pirates[2020 - syr]
233
+ irate2021 = pirates[2021 - syr]
234
+ # check implied inflation rate between 2018 and 2019 (before the reform)
235
+ grate = float(pol._II_em[2019 - syr]) / float(pol._II_em[2018 - syr])
236
+ assert round(grate - 1.0, 5) == round(irate2018, 5)
237
+ # check implied inflation rate between 2020 and 2021 (after the reform)
238
+ grate = float(pol._II_em[2021 - syr]) / float(pol._II_em[2020 - syr])
239
+ assert round(grate - 1.0, 5) == round(irate2020, 5)
240
+ # check implied inflation rate between 2021 and 2022 (after the reform)
241
+ grate = float(pol._II_em[2022 - syr]) / float(pol._II_em[2021 - syr])
242
+ assert round(grate - 1.0, 5) == round(irate2021, 5)
243
+
244
+
245
+ def test_multi_year_reform():
246
+ """
247
+ Test multi-year reform involving 1D and 2D parameters.
248
+ """
249
+ # specify dimensions of policy Policy object
250
+ syr = Policy.JSON_START_YEAR
251
+ nyrs = Policy.DEFAULT_NUM_YEARS
252
+ pol = Policy()
253
+ iratelist = pol.inflation_rates()
254
+ ifactor = {}
255
+ for i in range(0, nyrs):
256
+ ifactor[syr + i] = 1.0 + iratelist[i]
257
+ wratelist = pol.wage_growth_rates()
258
+ wfactor = {}
259
+ for i in range(0, nyrs):
260
+ wfactor[syr + i] = 1.0 + wratelist[i]
261
+ # specify multi-year reform using a param:year:value-fomatted dictionary
262
+ reform = {
263
+ 'SS_Earnings_c': {2016: 300000,
264
+ 2017: 500000,
265
+ 2019: 700000},
266
+ 'SS_Earnings_c-indexed': {2017: False,
267
+ 2019: True},
268
+ 'CTC_c': {2015: 2000},
269
+ 'EITC_c': {2016: [900, 5000, 8000, 9000],
270
+ 2019: [1200, 7000, 10000, 12000]},
271
+ 'II_em': {2016: 7000,
272
+ 2019: 9000}
273
+ }
274
+ # implement multi-year reform
275
+ pol.implement_reform(reform)
276
+ assert pol.current_year == syr
277
+ # move policy Policy object forward in time so current_year is syr+2
278
+ # Note: this would be typical usage because the first budget year
279
+ # is typically greater than Policy start_year.
280
+ pol.set_year(pol.start_year + 2)
281
+ assert pol.current_year == syr + 2
282
+ # confirm that actual parameters have expected post-reform values
283
+ check_eitc_c(pol, reform, ifactor)
284
+ check_ii_em(pol, reform, ifactor)
285
+ check_ss_earnings_c(pol, reform, wfactor)
286
+ check_ctc_c(pol, reform)
287
+ # end of test_multi_year_reform with the check_* functions below:
288
+
289
+
290
+ def check_ctc_c(ppo, reform):
291
+ """
292
+ Compare actual and expected _CTC_c parameter values
293
+ generated by the test_multi_year_reform() function above.
294
+ Ensure that future-year values in policy_current_law.json
295
+ are overwritten by reform.
296
+ """
297
+ actual = {}
298
+ arr = getattr(ppo, '_CTC_c')
299
+ for i in range(0, ppo.num_years):
300
+ actual[ppo.start_year + i] = arr[i]
301
+ assert actual[2013] == 1000
302
+ assert actual[2014] == 1000
303
+ e2015 = reform['CTC_c'][2015]
304
+ assert actual[2015] == e2015
305
+ e2016 = actual[2015]
306
+ assert actual[2016] == e2016
307
+ e2017 = actual[2016]
308
+ assert actual[2017] == e2017
309
+ e2018 = actual[2017]
310
+ assert actual[2018] == e2018
311
+ e2019 = actual[2018]
312
+ assert actual[2019] == e2019
313
+
314
+
315
+ def check_eitc_c(ppo, reform, ifactor):
316
+ """
317
+ Compare actual and expected _EITC_c parameter values
318
+ generated by the test_multi_year_reform() function above.
319
+ """
320
+ actual = {}
321
+ arr = getattr(ppo, '_EITC_c')
322
+ alen = len(arr[0])
323
+ for i in range(0, ppo.num_years):
324
+ actual[ppo.start_year + i] = arr[i]
325
+ assert np.allclose(actual[2013], [487, 3250, 5372, 6044],
326
+ atol=0.01, rtol=0.0)
327
+ assert np.allclose(actual[2014], [496, 3305, 5460, 6143],
328
+ atol=0.01, rtol=0.0)
329
+ assert np.allclose(actual[2015], [503, 3359, 5548, 6242],
330
+ atol=0.01, rtol=0.0)
331
+ e2016 = reform['EITC_c'][2016]
332
+ assert np.allclose(actual[2016], e2016, atol=0.01, rtol=0.0)
333
+ e2017 = [ifactor[2016] * actual[2016][j] for j in range(0, alen)]
334
+ assert np.allclose(actual[2017], e2017, atol=0.01, rtol=0.0)
335
+ e2018 = [ifactor[2017] * actual[2017][j] for j in range(0, alen)]
336
+ assert np.allclose(actual[2018], e2018, atol=0.01, rtol=0.0)
337
+ e2019 = reform['EITC_c'][2019]
338
+ assert np.allclose(actual[2019], e2019, atol=0.01, rtol=0.0)
339
+ e2020 = [ifactor[2019] * actual[2019][j] for j in range(0, alen)]
340
+ assert np.allclose(actual[2020], e2020, atol=0.01, rtol=0.0)
341
+ e2021 = [ifactor[2020] * actual[2020][j] for j in range(0, alen)]
342
+ assert np.allclose(actual[2021], e2021, atol=0.01, rtol=0.0)
343
+ e2022 = [ifactor[2021] * actual[2021][j] for j in range(0, alen)]
344
+ assert np.allclose(actual[2022], e2022, atol=0.01, rtol=0.0)
345
+
346
+
347
+ def check_ii_em(ppo, reform, ifactor):
348
+ """
349
+ Compare actual and expected _II_em parameter values
350
+ generated by the test_multi_year_reform() function above.
351
+ """
352
+ actual = {}
353
+ arr = getattr(ppo, '_II_em')
354
+ for i in range(0, ppo.num_years):
355
+ actual[ppo.start_year + i] = arr[i]
356
+ assert actual[2013] == 3900
357
+ assert actual[2014] == 3950
358
+ assert actual[2015] == 4000
359
+ e2016 = reform['II_em'][2016]
360
+ assert actual[2016] == e2016
361
+ e2017 = ifactor[2016] * actual[2016]
362
+ assert np.allclose([actual[2017]], [e2017], atol=0.01, rtol=0.0)
363
+ e2018 = ifactor[2017] * actual[2017]
364
+ assert np.allclose([actual[2018]], [e2018], atol=0.01, rtol=0.0)
365
+ e2019 = reform['II_em'][2019]
366
+ assert actual[2019] == e2019
367
+ e2020 = ifactor[2019] * actual[2019]
368
+ assert np.allclose([actual[2020]], [e2020], atol=0.01, rtol=0.0)
369
+ e2021 = ifactor[2020] * actual[2020]
370
+ assert np.allclose([actual[2021]], [e2021], atol=0.01, rtol=0.0)
371
+ e2022 = ifactor[2021] * actual[2021]
372
+ assert np.allclose([actual[2022]], [e2022], atol=0.01, rtol=0.0)
373
+
374
+
375
+ def check_ss_earnings_c(ppo, reform, wfactor):
376
+ """
377
+ Compare actual and expected _SS_Earnings_c parameter values
378
+ generated by the test_multi_year_reform() function above.
379
+ """
380
+ actual = {}
381
+ arr = getattr(ppo, '_SS_Earnings_c')
382
+ for i in range(0, ppo.num_years):
383
+ actual[ppo.start_year + i] = arr[i]
384
+ assert actual[2013] == 113700
385
+ assert actual[2014] == 117000
386
+ assert actual[2015] == 118500
387
+ e2016 = reform['SS_Earnings_c'][2016]
388
+ assert actual[2016] == e2016
389
+ e2017 = reform['SS_Earnings_c'][2017]
390
+ assert actual[2017] == e2017
391
+ e2018 = actual[2017] # no indexing after 2017
392
+ assert actual[2018] == e2018
393
+ e2019 = reform['SS_Earnings_c'][2019]
394
+ assert actual[2019] == e2019
395
+ e2020 = wfactor[2019] * actual[2019] # indexing after 2019
396
+ assert np.allclose([actual[2020]], [e2020], atol=0.01, rtol=0.0)
397
+ e2021 = wfactor[2020] * actual[2020]
398
+ assert np.allclose([actual[2021]], [e2021], atol=0.01, rtol=0.0)
399
+ e2022 = wfactor[2021] * actual[2021]
400
+ assert np.allclose([actual[2022]], [e2022], atol=0.01, rtol=0.0)
401
+
402
+
403
+ def test_policy_metadata():
404
+ """
405
+ Test that metadata() method returns expected dictionary.
406
+ """
407
+ clp = Policy()
408
+ mdata = clp.metadata()
409
+ assert mdata
410
+
411
+
412
+ def test_implement_reform_raises_on_no_year():
413
+ """
414
+ Test that implement_reform raises error for missing year.
415
+ """
416
+ reform = {'STD_Aged': [1400, 1200, 1400, 1400, 1400]}
417
+ ppo = Policy()
418
+ with pytest.raises(pt.ValidationError):
419
+ ppo.implement_reform(reform)
420
+
421
+
422
+ def test_implement_reform_raises_on_early_year():
423
+ """
424
+ Test that implement_reform raises error for early year.
425
+ """
426
+ ppo = Policy()
427
+ reform = {'STD_Aged': {2010: [1400, 1100, 1100, 1400, 1400]}}
428
+ with pytest.raises(pt.ValidationError):
429
+ ppo.implement_reform(reform)
430
+
431
+
432
+ def test_reform_with_default_indexed():
433
+ """
434
+ Test that implement_reform indexes after first reform year.
435
+ """
436
+ ppo = Policy()
437
+ reform = {'II_em': {2015: 4300}}
438
+ ppo.implement_reform(reform)
439
+ # II_em has a default indexing status of true, so
440
+ # in 2016 its value should be greater than 4300
441
+ ppo.set_year(2016)
442
+ assert ppo.II_em > 4300
443
+
444
+
445
+ def test_reform_makes_no_changes_before_year():
446
+ """
447
+ Test that implement_reform makes no changes before first reform year.
448
+ """
449
+ ppo = Policy()
450
+ reform = {'II_em': {2015: 4400}, 'II_em-indexed': {2015: True}}
451
+ ppo.implement_reform(reform)
452
+ ppo.set_year(2015)
453
+ assert np.allclose(ppo._II_em[:3], np.array([3900, 3950, 4400]),
454
+ atol=0.01, rtol=0.0)
455
+ assert ppo.II_em == 4400
456
+
457
+
458
+ @pytest.mark.parametrize("set_year", [False, True])
459
+ def test_read_json_reform_and_implement_reform(set_year):
460
+ """
461
+ Test reading and translation of reform file into a reform dictionary
462
+ that is then used to call implement_reform method.
463
+ NOTE: implement_reform called when policy.current_year == policy.start_year
464
+ """
465
+ reform_json = """
466
+ // Example of JSON reform text suitable for the
467
+ // Policy.read_json_reform() method.
468
+ // This JSON text can contain any number of trailing //-style comments,
469
+ // which will be removed before the contents are converted from JSON to
470
+ // a dictionary.
471
+ // The primary keys are policy parameters and secondary keys are years.
472
+ // Both the primary & secondary key values must be enclosed in quotes (").
473
+ // Boolean variables are specified as true or false with no quotes and all
474
+ // lowercase characters.
475
+ {
476
+ "AMT_brk1": // top of first AMT tax bracket
477
+ {"2015": 200000,
478
+ "2017": 300000
479
+ },
480
+ "EITC_c": // max EITC amount by number of qualifying kids (0,1,2,3+)
481
+ {"2016": [ 900, 5000, 8000, 9000],
482
+ "2019": [1200, 7000, 10000, 12000]
483
+ },
484
+ "II_em": // personal exemption amount (see indexing changes below)
485
+ {"2016": 6000,
486
+ "2018": 7500,
487
+ "2020": 9000
488
+ },
489
+ "II_em-indexed": // personal exemption amount indexing status
490
+ {"2016": false, // values in future years are same as this year value
491
+ "2018": true // vals in future years indexed with this year as base
492
+ },
493
+ "SS_Earnings_c": // Social Security (OASDI) maximum taxable earnings
494
+ {"2016": 300000,
495
+ "2018": 500000,
496
+ "2020": 700000
497
+ },
498
+ "AMT_em-indexed": // AMT exemption amount indexing status
499
+ {"2017": false, // values in future years are same as this year value
500
+ "2020": true // vals in future years indexed with this year as base
501
+ }
502
+ }
503
+ """
504
+ policy = Policy()
505
+ if set_year:
506
+ policy.set_year(2015)
507
+ reform_dict = Policy.read_json_reform(reform_json)
508
+ policy.implement_reform(reform_dict)
509
+ syr = policy.start_year
510
+ amt_brk1 = policy._AMT_brk1
511
+ assert amt_brk1[2015 - syr] == 200000
512
+ assert amt_brk1[2016 - syr] > 200000
513
+ assert amt_brk1[2017 - syr] == 300000
514
+ assert amt_brk1[2018 - syr] > 300000
515
+ ii_em = policy._II_em
516
+ assert ii_em[2016 - syr] == 6000
517
+ assert ii_em[2017 - syr] == 6000
518
+ assert ii_em[2018 - syr] == 7500
519
+ assert ii_em[2019 - syr] > 7500
520
+ assert ii_em[2020 - syr] == 9000
521
+ assert ii_em[2021 - syr] > 9000
522
+ amt_em = policy._AMT_em
523
+ assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0]
524
+ assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0]
525
+ assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0]
526
+ assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0]
527
+ assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0]
528
+ assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0]
529
+ assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0]
530
+ add4aged = policy._ID_Medical_frt_add4aged
531
+ assert add4aged[2015 - syr] == -0.025
532
+ assert add4aged[2016 - syr] == -0.025
533
+ assert add4aged[2017 - syr] == 0.0
534
+ assert add4aged[2022 - syr] == 0.0
535
+
536
+
537
+ def test_pop_the_cap_reform():
538
+ """
539
+ Test eliminating the maximum taxable earnings (MTE)
540
+ used in the calculation of the OASDI payroll tax.
541
+ """
542
+ # create Policy parameters object
543
+ ppo = Policy()
544
+ assert ppo.current_year == Policy.JSON_START_YEAR
545
+ # confirm that MTE has current-law values in 2015 and 2016
546
+ mte = ppo._SS_Earnings_c
547
+ syr = Policy.JSON_START_YEAR
548
+ assert mte[2015 - syr] == 118500
549
+ assert mte[2016 - syr] == 118500
550
+ # specify a "pop the cap" reform that eliminates MTE cap in 2016
551
+ reform = {'SS_Earnings_c': {2016: 9e99}}
552
+ ppo.implement_reform(reform)
553
+ mte = ppo._SS_Earnings_c
554
+ assert mte[2015 - syr] == 118500
555
+ assert mte[2016 - syr] == 9e99
556
+ assert mte[ppo.end_year - syr] == 9e99
557
+
558
+
559
+ def test_order_of_indexing_and_level_reforms():
560
+ """
561
+ Test that the order of the two reform provisions for the same parameter
562
+ make no difference to the post-reform policy parameter values.
563
+ """
564
+ # specify two reforms that raises the MTE and stops its indexing in 2015
565
+ reform = [
566
+ {
567
+ 'SS_Earnings_c': {2015: 500000},
568
+ 'SS_Earnings_c-indexed': {2015: False}
569
+ },
570
+ # now reverse the order of the two reform provisions
571
+ {
572
+ 'SS_Earnings_c-indexed': {2015: False},
573
+ 'SS_Earnings_c': {2015: 500000}
574
+ }
575
+ ]
576
+ # specify two Policy objects
577
+ ppo = [Policy(), Policy()]
578
+ # apply reforms to corresponding Policy object & check post-reform values
579
+ syr = Policy.JSON_START_YEAR
580
+ for ref in range(len(reform)): # pylint: disable=consider-using-enumerate
581
+ # confirm pre-reform MTE values in 2014-2017
582
+ mte = ppo[ref]._SS_Earnings_c
583
+ assert mte[2014 - syr] == 117000
584
+ assert mte[2015 - syr] == 118500
585
+ assert mte[2016 - syr] == 118500
586
+ assert mte[2017 - syr] < 500000
587
+ # implement reform in 2015
588
+ ppo[ref].implement_reform(reform[ref])
589
+ # confirm post-reform MTE values in 2014-2017
590
+ mte = ppo[ref]._SS_Earnings_c
591
+ assert mte[2014 - syr] == 117000
592
+ assert mte[2015 - syr] == 500000
593
+ assert mte[2016 - syr] == 500000
594
+ assert mte[2017 - syr] == 500000
595
+
596
+
597
+ def test_misspecified_reform_dictionary():
598
+ """
599
+ Demonstrate pitfalls of careless specification of policy reform
600
+ dictionaries involving non-unique dictionary keys.
601
+ """
602
+ # specify apparently the same reform in two different ways, forgetting
603
+ # that Python dictionaries have unique keys
604
+ reform1 = {'II_em': {2019: 1000, 2020: 2000}}
605
+ # pylint: disable=duplicate-key
606
+ reform2 = {'II_em': {2019: 1000}, 'II_em': {2020: 2000}}
607
+ # these two reform dictionaries are not the same: the second
608
+ # 'II_em' key value for 2020 in reform2 OVERWRITES and REPLACES
609
+ # the first 'II_em' key value for 2019 in reform2
610
+ assert reform1 != reform2
611
+
612
+
613
+ def test_section_titles(tests_path):
614
+ """
615
+ Check section titles in policy_current_law.json and uguide.htmx files.
616
+ """
617
+ # pylint: disable=too-many-locals
618
+ def generate_section_dictionary(md_text):
619
+ """
620
+ Returns dictionary of section titles that is
621
+ structured like the VALID_SECTION dictionary (see below) and
622
+ extracted from the specified html_text.
623
+ """
624
+ sdict = {}
625
+ for line in md_text.splitlines():
626
+ # This is shown as an empty case in current law policy and
627
+ # validation.
628
+ if line.startswith('## Other Parameters (not in Tax-Brain webapp'):
629
+ sdict[''] = {}
630
+ sdict[''][''] = 0
631
+ continue
632
+ sec2line = line.startswith('### ')
633
+ sec1line = line.startswith('## ')
634
+ # Create outer-layer dictionary entry for sec1.
635
+ if sec1line:
636
+ sec1 = line.replace('##', '', 1).strip()
637
+ sdict[sec1] = {}
638
+ # Create inner dictionary entry for sec1-sec2.
639
+ # Note that sec1 will have been defined from a previous loop.
640
+ if sec2line:
641
+ sec2 = line.replace('###', '', 1).strip()
642
+ sdict[sec1][sec2] = 0
643
+ return sdict
644
+ # begin main logic of test_section_titles
645
+ # specify expected section titles ordered as on the Tax-Brain webapp
646
+ ided_ceiling_pct = ('Ceiling On The Benefit Of Itemized Deductions '
647
+ 'As A Percent Of Deductible Expenses')
648
+ cgqd_tax_same = ('Tax All Capital Gains And Dividends The Same '
649
+ 'As Regular Taxable Income')
650
+ valid_dict = {
651
+ '': { # empty section_1 implies parameter not displayed in Tax-Brain
652
+ '': 0
653
+ },
654
+ 'Parameter Indexing': {
655
+ 'Offsets': 0
656
+ },
657
+ 'Payroll Taxes': {
658
+ 'Social Security FICA': 0,
659
+ 'Medicare FICA': 0,
660
+ 'Additional Medicare FICA': 0
661
+ },
662
+ 'Social Security Taxability': {
663
+ 'Social Security Benefit Taxability': 0,
664
+ },
665
+ 'Above The Line Deductions': {
666
+ 'Misc. Adjustment Haircuts': 0,
667
+ 'Misc. Exclusions': 0,
668
+ 'Child And Elderly Care': 0
669
+ },
670
+ 'Personal Exemptions': {
671
+ 'Personal And Dependent Exemption Amount': 0,
672
+ # 'Personal Exemption Phaseout Starting Income': 0,
673
+ 'Personal Exemption Phaseout Rate': 0,
674
+ 'Repeal for Dependents Under Age 18': 0
675
+ },
676
+ 'Standard Deduction': {
677
+ 'Standard Deduction Amount': 0,
678
+ 'Additional Standard Deduction For Blind And Aged': 0
679
+ # 'Standard Deduction For Dependents': 0
680
+ },
681
+ 'Nonrefundable Credits': {
682
+ 'Misc. Credit Limits': 0,
683
+ 'Child And Dependent Care': 0,
684
+ 'Personal Nonrefundable Credit': 0
685
+ },
686
+ 'Child/Dependent Credits': {
687
+ 'Child Tax Credit': 0,
688
+ 'Additional Child Tax Credit': 0,
689
+ 'Other Dependent Tax Credit': 0
690
+ },
691
+ 'Itemized Deductions': {
692
+ 'Medical Expenses': 0,
693
+ 'State And Local Income And Sales Taxes': 0,
694
+ 'State, Local, And Foreign Real Estate Taxes': 0,
695
+ 'State And Local Taxes And Real Estate Taxes': 0,
696
+ 'Interest Paid': 0,
697
+ 'Charity': 0,
698
+ 'Casualty': 0,
699
+ 'Miscellaneous': 0,
700
+ 'Itemized Deduction Limitation': 0,
701
+ 'Surtax On Itemized Deduction Benefits Above An AGI Threshold': 0,
702
+ ided_ceiling_pct: 0,
703
+ 'Ceiling On The Amount Of Itemized Deductions Allowed': 0
704
+ },
705
+ 'Capital Gains And Dividends': {
706
+ 'Regular - Long Term Capital Gains And Qualified Dividends': 0,
707
+ 'AMT - Long Term Capital Gains And Qualified Dividends': 0,
708
+ cgqd_tax_same: 0
709
+ },
710
+ 'Personal Income': {
711
+ 'Regular: Non-AMT, Non-Pass-Through': 0,
712
+ 'Pass-Through': 0,
713
+ 'Alternative Minimum Tax': 0
714
+ },
715
+ 'Other Taxes': {
716
+ 'Net Investment Income Tax': 0
717
+ },
718
+ 'Refundable Credits': {
719
+ 'Earned Income Tax Credit': 0,
720
+ 'New Refundable Child Tax Credit': 0,
721
+ 'Personal Refundable Credit': 0,
722
+ 'Refundable Payroll Tax Credit': 0
723
+ },
724
+ 'Surtaxes': {
725
+ 'New Minimum Tax': 0,
726
+ 'New AGI Surtax': 0,
727
+ 'Lump-Sum Tax': 0
728
+ },
729
+ 'Universal Basic Income': {
730
+ 'UBI Benefits': 0,
731
+ 'UBI Taxability': 0
732
+ },
733
+ 'Benefits': {
734
+ 'Benefit Repeal': 0,
735
+ }
736
+ }
737
+ # check validity of parameter section titles in policy_current_law.json
738
+ path = os.path.join(tests_path, '..', 'policy_current_law.json')
739
+ with open(path, 'r', encoding='utf-8') as clpfile:
740
+ clpdict = json.load(clpfile)
741
+ clpdict.pop("schema", None)
742
+ # ... make sure ever clpdict section title is in valid_dict
743
+ clp_dict = {} # dictionary of clp section titles structured like valid
744
+ for pname in clpdict:
745
+ param = clpdict[pname]
746
+ assert isinstance(param, dict)
747
+ sec1title = param['section_1']
748
+ assert sec1title in valid_dict
749
+ sec2title = param['section_2']
750
+ assert sec2title in valid_dict[sec1title]
751
+ if sec1title not in clp_dict:
752
+ clp_dict[sec1title] = {}
753
+ if sec2title not in clp_dict[sec1title]:
754
+ clp_dict[sec1title][sec2title] = 0
755
+ # ... make sure every valid_dict section title is in clpdict
756
+ for sec1title, secdict in valid_dict.items():
757
+ assert isinstance(secdict, dict)
758
+ assert sec1title in clp_dict
759
+ for sec2title in secdict:
760
+ assert sec2title in clp_dict[sec1title]
761
+ # check validity of parameter section titles in docs/uguide.htmx skeleton
762
+ path = os.path.join(tests_path, '..', '..', 'docs', 'guide',
763
+ 'policy_params.md')
764
+ with open(path, 'r', encoding='utf-8') as md_file:
765
+ md_text = md_file.read()
766
+ md_dict = generate_section_dictionary(md_text)
767
+ # ... make sure every md_dict section title is in valid_dict
768
+ for sec1title,secdict in md_dict.items():
769
+ assert isinstance(secdict, dict)
770
+ assert sec1title in valid_dict
771
+ for sec2title in secdict:
772
+ assert sec2title in valid_dict[sec1title]
773
+ # ... make sure every valid_dict section title is in md_dict
774
+ for sec1title, secdict in valid_dict.items():
775
+ assert isinstance(secdict, dict)
776
+ assert sec1title in md_dict
777
+ for sec2title in secdict:
778
+ assert sec2title in md_dict[sec1title]
779
+
780
+
781
+ def test_description_punctuation(tests_path):
782
+ """
783
+ Check that each description ends in a period.
784
+ """
785
+ # read JSON file into a dictionary
786
+ path = os.path.join(tests_path, '..', 'policy_current_law.json')
787
+ with open(path, 'r', encoding='utf-8') as jsonfile:
788
+ dct = json.load(jsonfile)
789
+ dct.pop("schema", None)
790
+ all_desc_ok = True
791
+ for param in dct.keys():
792
+ if not dct[param]['description'].endswith('.'):
793
+ all_desc_ok = False
794
+ print('param,description=',
795
+ str(param),
796
+ dct[param]['description'])
797
+ assert all_desc_ok
798
+
799
+
800
+ def test_get_index_rate():
801
+ """
802
+ Test Parameters.get_index_rate.
803
+ """
804
+ pol = Policy()
805
+ wgrates = pol.get_index_rate('SS_Earnings_c', 2017)
806
+ pirates = pol.get_index_rate('II_em', 2017)
807
+ assert isinstance(wgrates, np.float64)
808
+ assert wgrates == pol.wage_growth_rates(2017)
809
+ assert pirates == pol.inflation_rates(2017)
810
+ assert isinstance(pirates, np.float64)
811
+ assert pol.inflation_rates() == pol._inflation_rates
812
+ assert pol.wage_growth_rates() == pol._wage_growth_rates
813
+
814
+
815
+ def test_reform_with_bad_ctc_levels():
816
+ """
817
+ Implement a reform with _ACTC > _CTC_c values.
818
+ """
819
+ pol = Policy()
820
+ child_credit_reform = {
821
+ 'CTC_c': {2020: 2200},
822
+ 'ACTC_c': {2020: 2500}
823
+ }
824
+ with pytest.raises(pt.ValidationError):
825
+ pol.implement_reform(child_credit_reform)
826
+
827
+
828
+ def test_reform_with_removed_parameter(monkeypatch):
829
+ """
830
+ Try to use removed parameter in a reform.
831
+ """
832
+ policy1 = Policy()
833
+ reform1 = {'FilerCredit_c': {2020: 1000}}
834
+ with pytest.raises(pt.ValidationError):
835
+ policy1.implement_reform(reform1)
836
+ policy2 = Policy()
837
+ reform2 = {'FilerCredit_c-indexed': {2020: True}}
838
+ with pytest.raises(pt.ValidationError):
839
+ policy2.implement_reform(reform2)
840
+
841
+ redefined_msg = {"some_redefined": "some_redefined was redefined."}
842
+ monkeypatch.setattr(Policy, "REDEFINED_PARAMS", redefined_msg)
843
+
844
+ pol = Policy()
845
+ with pytest.raises(pt.ValidationError):
846
+ pol.implement_reform({"some_redefined": "hello world"})
847
+
848
+
849
+ def test_reform_with_out_of_range_error():
850
+ """
851
+ Try to use out-of-range values versus other parameter values in a reform.
852
+ """
853
+ pol = Policy()
854
+ reform = {'SS_thd85': {2020: [20000, 20000, 20000, 20000, 20000]}}
855
+ pol.implement_reform(reform, raise_errors=False)
856
+ assert pol.parameter_errors
857
+
858
+
859
+ def test_reform_with_warning():
860
+ """
861
+ Try to use warned out-of-range parameter value in reform.
862
+ """
863
+ exp_warnings = {
864
+ 'ID_Medical_frt': [
865
+ 'ID_Medical_frt[year=2020] 0.05 < min 0.075 '
866
+ ]
867
+ }
868
+ pol = Policy()
869
+ reform = {'ID_Medical_frt': {2020: 0.05}}
870
+
871
+ pol.implement_reform(reform, print_warnings=True)
872
+ assert pol.warnings == exp_warnings
873
+ pol.set_state(year=2020)
874
+ assert pol.ID_Medical_frt == np.array([0.05])
875
+
876
+ pol.implement_reform(reform, print_warnings=False)
877
+ assert pol.warnings == {}
878
+ pol.set_state(year=2020)
879
+ assert pol.ID_Medical_frt == np.array([0.05])
880
+
881
+
882
+ def test_reform_with_scalar_vector_errors():
883
+ """
884
+ Test catching scalar-vector confusion.
885
+ """
886
+ policy1 = Policy()
887
+ reform1 = {'SS_thd85': {2020: 30000}}
888
+ with pytest.raises(pt.ValidationError):
889
+ policy1.implement_reform(reform1)
890
+
891
+ policy2 = Policy()
892
+ reform2 = {'ID_Medical_frt': {2020: [0.08]}}
893
+ with pytest.raises(pt.ValidationError):
894
+ policy2.implement_reform(reform2)
895
+
896
+ policy3 = Policy()
897
+ reform3 = {'ID_Medical_frt': [{"year": 2020, "value": [0.08]}]}
898
+ with pytest.raises(pt.ValidationError):
899
+ policy3.adjust(reform3)
900
+
901
+ # Check that error is thrown if there are extra elements in array.
902
+ policy4 = Policy()
903
+ ref4 = {"II_brk1": {2020: [9700, 19400, 9700, 13850, 19400, 19400]}}
904
+ with pytest.raises(pt.ValidationError):
905
+ policy4.implement_reform(ref4)
906
+
907
+ policy5 = Policy()
908
+ ref5 = {"II_rt1": {2029: [.2, .3]}}
909
+ with pytest.raises(pt.ValidationError):
910
+ policy5.implement_reform(ref5)
911
+
912
+
913
+ def test_index_offset_reform():
914
+ """
915
+ Test a reform that includes both a change in parameter_indexing_CPI_offset
916
+ and a change in a variable's indexed status in the same year.
917
+ """
918
+ # create policy0 to extract inflation rates before any
919
+ # parameter_indexing_CPI_offset
920
+ policy0 = Policy()
921
+ policy0.implement_reform({'parameter_indexing_CPI_offset': {2017: 0}})
922
+ cpiu_rates = policy0.inflation_rates()
923
+
924
+ reform1 = {'CTC_c-indexed': {2020: True}}
925
+ policy1 = Policy()
926
+ policy1.implement_reform(reform1)
927
+ offset = -0.005
928
+ reform2 = {'CTC_c-indexed': {2020: True},
929
+ 'parameter_indexing_CPI_offset': {2020: offset}}
930
+ policy2 = Policy()
931
+ policy2.implement_reform(reform2) # caused T-C crash before PR#2364
932
+ # extract from policy1 and policy2 the parameter values of CTC_c
933
+ pvalue1 = {}
934
+ pvalue2 = {}
935
+ for cyr in [2019, 2020, 2021]:
936
+ policy1.set_year(cyr)
937
+ pvalue1[cyr] = policy1.CTC_c[0]
938
+ policy2.set_year(cyr)
939
+ pvalue2[cyr] = policy2.CTC_c[0]
940
+ # check that pvalue1 and pvalue2 dictionaries contain the expected values
941
+ assert pvalue2[2019] == pvalue1[2019]
942
+ assert pvalue2[2020] == pvalue1[2020]
943
+ assert pvalue2[2020] == pvalue2[2019]
944
+ # ... indexing of CTC_c begins shows up first in 2021 parameter values
945
+ assert pvalue1[2021] > pvalue1[2020]
946
+ assert pvalue2[2021] > pvalue2[2020]
947
+ # ... calculate expected pvalue2[2021] from inflation rates and offset
948
+ syear = Policy.JSON_START_YEAR
949
+ expindexrate = cpiu_rates[2020 - syear] + offset
950
+ expvalue = round(pvalue2[2020] * (1. + expindexrate), 2)
951
+ # ... compare expected value with actual value of pvalue2 for 2021
952
+ assert np.allclose([expvalue], [pvalue2[2021]])
953
+
954
+
955
+ def test_cpi_offset_affect_on_prior_years():
956
+ """
957
+ Test that parameter_indexing_CPI_offset does not have affect
958
+ on inflation rates in earlier years.
959
+ """
960
+ reform1 = {'parameter_indexing_CPI_offset': {2022: 0}}
961
+ reform2 = {'parameter_indexing_CPI_offset': {2022: -0.005}}
962
+ p1 = Policy()
963
+ p2 = Policy()
964
+ p1.implement_reform(reform1)
965
+ p2.implement_reform(reform2)
966
+
967
+ start_year = p1.start_year
968
+ p1_rates = np.array(p1.inflation_rates())
969
+ p2_rates = np.array(p2.inflation_rates())
970
+
971
+ # Inflation rates prior to 2022 are the same.
972
+ np.testing.assert_allclose(
973
+ p1_rates[:2022 - start_year],
974
+ p2_rates[:2022 - start_year]
975
+ )
976
+
977
+ # Inflation rate in 2022 was updated.
978
+ np.testing.assert_allclose(
979
+ p1_rates[2022 - start_year],
980
+ p2_rates[2022 - start_year] - (-0.005)
981
+ )
982
+
983
+
984
+ def test_cpi_offset_on_reverting_params():
985
+ """
986
+ Test that params that revert to their pre-TCJA values
987
+ in 2026 revert if a parameter_indexing_CPI_offset is specified.
988
+ """
989
+ reform0 = {'parameter_indexing_CPI_offset': {2020: -0.001}}
990
+ reform1 = {'STD': {2017: [6350, 12700, 6350, 9350, 12700]},
991
+ 'parameter_indexing_CPI_offset': {2020: -0.001}}
992
+ reform2 = {'STD': {2020: [10000, 20000, 10000, 10000, 20000]},
993
+ 'parameter_indexing_CPI_offset': {2020: -0.001}}
994
+
995
+ p0 = Policy()
996
+ p1 = Policy()
997
+ p2 = Policy()
998
+ p0.implement_reform(reform0)
999
+ p1.implement_reform(reform1)
1000
+ p2.implement_reform(reform2)
1001
+
1002
+ ryear = 2026
1003
+ syear = Policy.JSON_START_YEAR
1004
+
1005
+ # STD was reverted in 2026
1006
+ # atol=0.5 because ppp.py rounds params to nearest int
1007
+ assert np.allclose(
1008
+ p0._STD[ryear - syear],
1009
+ p1._STD[ryear - syear], atol=0.5)
1010
+
1011
+ # STD was not reverted in 2026 if included in revision
1012
+ assert not np.allclose(
1013
+ p1._STD[ryear - syear],
1014
+ p2._STD[ryear - syear], atol=0.5)
1015
+
1016
+
1017
+ def test_raise_errors_regression():
1018
+ """
1019
+ This tests that raise_errors prevents the error from being thrown. The
1020
+ correct behavior is to exit the `adjust` function and store the errors.
1021
+ """
1022
+ ref = {
1023
+ "II_brk7-indexed": [{"value": True}],
1024
+ "II_brk6": [{"value": 316700, "MARS": "single", "year": 2020}],
1025
+ "II_brk7": [{"value": 445400, "MARS": "single", "year": 2020}],
1026
+
1027
+ }
1028
+ pol = Policy()
1029
+ pol.adjust(ref, raise_errors=False)
1030
+ assert pol.errors
1031
+
1032
+
1033
+ def test_simple_adj():
1034
+ """
1035
+ Test updating a 2D parameter that is indexed to inflation.
1036
+ """
1037
+ pol1 = Policy()
1038
+ pol1.implement_reform(
1039
+ {
1040
+ "EITC_c": {
1041
+ 2020: [10000, 10001, 10002, 10003],
1042
+ 2023: [20000, 20001, 20002, 20003],
1043
+ }
1044
+ }
1045
+ )
1046
+ pol2 = Policy()
1047
+ pol2.adjust(
1048
+ {
1049
+ "EITC_c": [
1050
+ {"year": 2020, "EIC": "0kids", "value": 10000},
1051
+ {"year": 2020, "EIC": "1kid", "value": 10001},
1052
+ {"year": 2020, "EIC": "2kids", "value": 10002},
1053
+ {"year": 2020, "EIC": "3+kids", "value": 10003},
1054
+ {"year": 2023, "EIC": "0kids", "value": 20000},
1055
+ {"year": 2023, "EIC": "1kid", "value": 20001},
1056
+ {"year": 2023, "EIC": "2kids", "value": 20002},
1057
+ {"year": 2023, "EIC": "3+kids", "value": 20003},
1058
+ ]
1059
+ }
1060
+ )
1061
+ cmp_policy_objs(pol1, pol2)
1062
+
1063
+ pol0 = Policy()
1064
+ pol0.set_year(2019)
1065
+ pol2.set_year(2019)
1066
+
1067
+ assert np.allclose(pol0.EITC_c, pol2.EITC_c)
1068
+
1069
+ pol2.set_state(year=[2020, 2021, 2022, 2023, 2024])
1070
+ val2020 = np.array([[10000, 10001, 10002, 10003]])
1071
+ val2023 = np.array([[20000, 20001, 20002, 20003]])
1072
+
1073
+ exp = np.vstack([
1074
+ val2020,
1075
+ val2020 * (1 + pol2.inflation_rates(year=2020)),
1076
+ (
1077
+ val2020 * (1 + pol2.inflation_rates(year=2020))
1078
+ ).round(2) * (1 + pol2.inflation_rates(year=2021)),
1079
+ val2023,
1080
+ val2023 * (1 + pol2.inflation_rates(year=2023)),
1081
+ ]).round(2)
1082
+ np.testing.assert_allclose(pol2.EITC_c, exp)
1083
+
1084
+
1085
+ def test_adj_without_index_1():
1086
+ """
1087
+ Test update indexed parameter after turning off its indexed status.
1088
+ """
1089
+ pol1 = Policy()
1090
+ pol1.implement_reform(
1091
+ {
1092
+ "EITC_c": {
1093
+ 2020: [10000, 10001, 10002, 10003],
1094
+ 2023: [20000, 20001, 20002, 20003],
1095
+ },
1096
+ "EITC_c-indexed": {2019: False},
1097
+ }
1098
+ )
1099
+ pol2 = Policy()
1100
+ pol2.adjust(
1101
+ {
1102
+ "EITC_c": [
1103
+ {"year": 2020, "EIC": "0kids", "value": 10000},
1104
+ {"year": 2020, "EIC": "1kid", "value": 10001},
1105
+ {"year": 2020, "EIC": "2kids", "value": 10002},
1106
+ {"year": 2020, "EIC": "3+kids", "value": 10003},
1107
+ {"year": 2023, "EIC": "0kids", "value": 20000},
1108
+ {"year": 2023, "EIC": "1kid", "value": 20001},
1109
+ {"year": 2023, "EIC": "2kids", "value": 20002},
1110
+ {"year": 2023, "EIC": "3+kids", "value": 20003},
1111
+ ],
1112
+ "EITC_c-indexed": [{"year": 2019, "value": False}],
1113
+ }
1114
+ )
1115
+ cmp_policy_objs(pol1, pol2)
1116
+
1117
+ pol0 = Policy()
1118
+ pol0.set_year(2019)
1119
+ pol2.set_year(2019)
1120
+
1121
+ assert np.allclose(pol0.EITC_c, pol2.EITC_c)
1122
+
1123
+ pol2.set_state(year=[2020, 2021, 2022, 2023, 2024])
1124
+
1125
+ val2020 = np.array([[10000, 10001, 10002, 10003]])
1126
+ val2023 = np.array([[20000, 20001, 20002, 20003]])
1127
+
1128
+ exp = np.vstack([
1129
+ val2020,
1130
+ val2020,
1131
+ val2020,
1132
+ val2023,
1133
+ val2023,
1134
+ ]).round(2)
1135
+ np.testing.assert_allclose(pol2.EITC_c, exp)
1136
+
1137
+
1138
+ def test_adj_without_index_2():
1139
+ """
1140
+ Test updating an indexed parameter, making it unindexed,
1141
+ and then adjusting it again.
1142
+ """
1143
+ pol1 = Policy()
1144
+ pol1.implement_reform(
1145
+ {
1146
+ "EITC_c": {
1147
+ 2020: [10000, 10001, 10002, 10003],
1148
+ 2023: [20000, 20001, 20002, 20003],
1149
+ },
1150
+ "EITC_c-indexed": {2022: False},
1151
+ }
1152
+ )
1153
+ pol2 = Policy()
1154
+ pol2.adjust(
1155
+ {
1156
+ "EITC_c": [
1157
+ {"year": 2020, "EIC": "0kids", "value": 10000},
1158
+ {"year": 2020, "EIC": "1kid", "value": 10001},
1159
+ {"year": 2020, "EIC": "2kids", "value": 10002},
1160
+ {"year": 2020, "EIC": "3+kids", "value": 10003},
1161
+ {"year": 2023, "EIC": "0kids", "value": 20000},
1162
+ {"year": 2023, "EIC": "1kid", "value": 20001},
1163
+ {"year": 2023, "EIC": "2kids", "value": 20002},
1164
+ {"year": 2023, "EIC": "3+kids", "value": 20003},
1165
+ ],
1166
+ "EITC_c-indexed": [{"year": 2022, "value": False}],
1167
+ }
1168
+ )
1169
+ cmp_policy_objs(pol1, pol2)
1170
+
1171
+ pol0 = Policy()
1172
+ pol0.set_year(2019)
1173
+ pol2.set_year(2019)
1174
+
1175
+ assert np.allclose(pol0.EITC_c, pol2.EITC_c)
1176
+
1177
+ pol2.set_state(year=[2020, 2021, 2022, 2023, 2024])
1178
+
1179
+ val2020 = np.array([[10000, 10001, 10002, 10003]])
1180
+ val2023 = np.array([[20000, 20001, 20002, 20003]])
1181
+
1182
+ exp = np.vstack([
1183
+ val2020,
1184
+ val2020 * (1 + pol2.inflation_rates(year=2020)),
1185
+ (
1186
+ val2020 * (1 + pol2.inflation_rates(year=2020))
1187
+ ).round(2) * (1 + pol2.inflation_rates(year=2021)),
1188
+ val2023,
1189
+ val2023,
1190
+ ]).round(2)
1191
+ np.testing.assert_allclose(pol2.EITC_c, exp)
1192
+
1193
+
1194
+ def test_activate_index():
1195
+ """
1196
+ Test changing a non-indexed parameter to an indexed parameter.
1197
+ """
1198
+ pol1 = Policy()
1199
+ pol1.implement_reform({
1200
+ "CTC_c": {2022: 2000},
1201
+ "CTC_c-indexed": {2022: True}
1202
+ })
1203
+ pol2 = Policy()
1204
+ pol2.adjust(
1205
+ {
1206
+ "CTC_c": [{"year": 2022, "value": 2000}],
1207
+ "CTC_c-indexed": [{"year": 2022, "value": True}],
1208
+ }
1209
+ )
1210
+ cmp_policy_objs(pol1, pol2)
1211
+
1212
+ pol0 = Policy()
1213
+ pol0.set_year(year=2021)
1214
+ pol2.set_state(year=[2021, 2022, 2023])
1215
+ exp = np.array([
1216
+ pol0.CTC_c[0],
1217
+ 2000,
1218
+ 2000 * (1 + pol2.inflation_rates(year=2022))
1219
+ ]).round(2)
1220
+
1221
+ np.testing.assert_allclose(pol2.CTC_c, exp)
1222
+
1223
+
1224
+ def test_apply_cpi_offset():
1225
+ """
1226
+ Test applying the parameter_indexing_CPI_offset parameter
1227
+ without any other parameters.
1228
+ """
1229
+ pol1 = Policy()
1230
+ pol1.implement_reform(
1231
+ {"parameter_indexing_CPI_offset": {2021: -0.001}}
1232
+ )
1233
+ pol2 = Policy()
1234
+ pol2.adjust(
1235
+ {"parameter_indexing_CPI_offset": [
1236
+ {"year": 2021, "value": -0.001}
1237
+ ]}
1238
+ )
1239
+ cmp_policy_objs(pol1, pol2)
1240
+
1241
+ pol0 = Policy()
1242
+ pol0.implement_reform({"parameter_indexing_CPI_offset": {2021: 0}})
1243
+
1244
+ init_rates = pol0.inflation_rates()
1245
+ new_rates = pol2.inflation_rates()
1246
+
1247
+ start_ix = 2021 - pol2.start_year
1248
+
1249
+ exp_rates = copy.deepcopy(new_rates)
1250
+ exp_rates[start_ix:] -= pol2._parameter_indexing_CPI_offset[start_ix:]
1251
+ np.testing.assert_allclose(init_rates, exp_rates)
1252
+
1253
+ # make sure values prior to 2021 were not affected.
1254
+ cmp_policy_objs(pol0, pol2, year_range=range(pol2.start_year, 2021))
1255
+
1256
+ test_year = Policy.LAST_KNOWN_YEAR
1257
+ pol2.set_state(year=[test_year, test_year + 1])
1258
+ np.testing.assert_equal(
1259
+ (pol2.EITC_c[1] / pol2.EITC_c[0] - 1).round(4),
1260
+ (pol0.inflation_rates(year=test_year) + (-0.001)).round(4),
1261
+ )
1262
+
1263
+
1264
+ def test_multiple_cpi_swaps():
1265
+ """
1266
+ Test changing a parameter's indexed status multiple times.
1267
+ """
1268
+ pol1 = Policy()
1269
+ pol1.implement_reform(
1270
+ {
1271
+ "II_em": {2016: 6000, 2018: 7500, 2020: 9000},
1272
+ "II_em-indexed": {2016: False, 2018: True},
1273
+ }
1274
+ )
1275
+ pol2 = Policy()
1276
+ pol2.adjust(
1277
+ {
1278
+ "II_em": [
1279
+ {"year": 2016, "value": 6000},
1280
+ {"year": 2018, "value": 7500},
1281
+ {"year": 2020, "value": 9000},
1282
+ ],
1283
+ "II_em-indexed": [
1284
+ {"year": 2016, "value": False},
1285
+ {"year": 2018, "value": True},
1286
+ ],
1287
+ }
1288
+ )
1289
+ cmp_policy_objs(pol1, pol2)
1290
+
1291
+ # check inflation is not applied.
1292
+ pol2.set_state(year=[2016, 2017])
1293
+ np.testing.assert_equal(
1294
+ pol2.II_em[0], pol2.II_em[1]
1295
+ )
1296
+
1297
+ # check inflation rate is applied.
1298
+ pol2.set_state(year=[2018, 2019])
1299
+ np.testing.assert_equal(
1300
+ (pol2.II_em[1] / pol2.II_em[0] - 1).round(4),
1301
+ pol2.inflation_rates(year=2018),
1302
+ )
1303
+
1304
+ # check inflation rate applied for rest of window.
1305
+ window = list(range(2020, pol2.end_year + 1))
1306
+ pol2.set_state(year=window)
1307
+ np.testing.assert_equal(
1308
+ (pol2.II_em[1:] / pol2.II_em[:-1] - 1).round(4),
1309
+ [pol2.inflation_rates(year=year) for year in window[:-1]],
1310
+ )
1311
+
1312
+
1313
+ def test_multiple_cpi_swaps2():
1314
+ """
1315
+ Test changing the indexed status of multiple parameters multiple times.
1316
+ """
1317
+ pol1 = Policy()
1318
+ pol1.implement_reform(
1319
+ {
1320
+ "II_em": {2016: 6000, 2018: 7500, 2020: 9000},
1321
+ "II_em-indexed": {2016: False, 2018: True},
1322
+ "SS_Earnings_c": {2016: 300000, 2018: 500000},
1323
+ "SS_Earnings_c-indexed": {2017: False, 2019: True},
1324
+ "AMT_em-indexed": {2017: False, 2020: True},
1325
+ }
1326
+ )
1327
+ pol2 = Policy()
1328
+ pol2.adjust(
1329
+ {
1330
+ "SS_Earnings_c": [
1331
+ {"year": 2016, "value": 300000},
1332
+ {"year": 2018, "value": 500000},
1333
+ ],
1334
+ "SS_Earnings_c-indexed": [
1335
+ {"year": 2017, "value": False},
1336
+ {"year": 2019, "value": True},
1337
+ ],
1338
+ "AMT_em-indexed": [
1339
+ {"year": 2017, "value": False},
1340
+ {"year": 2020, "value": True},
1341
+ ],
1342
+ "II_em": [
1343
+ {"year": 2016, "value": 6000},
1344
+ {"year": 2018, "value": 7500},
1345
+ {"year": 2020, "value": 9000},
1346
+ ],
1347
+ "II_em-indexed": [
1348
+ {"year": 2016, "value": False},
1349
+ {"year": 2018, "value": True},
1350
+ ],
1351
+ }
1352
+ )
1353
+ cmp_policy_objs(pol1, pol2)
1354
+
1355
+ # Test SS_Earnings_c
1356
+ # check inflation is still applied from 2016 to 2017.
1357
+ pol2.set_state(year=[2016, 2017])
1358
+ np.testing.assert_equal(
1359
+ (pol2.SS_Earnings_c[1] / pol2.SS_Earnings_c[0] - 1).round(4),
1360
+ pol2.wage_growth_rates(year=2016),
1361
+ )
1362
+
1363
+ # check inflation rate is not applied after adjustment in 2018.
1364
+ pol2.set_state(year=[2018, 2019])
1365
+ np.testing.assert_equal(
1366
+ pol2.SS_Earnings_c[0], pol2.SS_Earnings_c[1]
1367
+ )
1368
+
1369
+ # check inflation rate applied for rest of window.
1370
+ window = list(range(2019, pol2.end_year + 1))
1371
+ pol2.set_state(year=window)
1372
+ np.testing.assert_equal(
1373
+ (pol2.SS_Earnings_c[1:] / pol2.SS_Earnings_c[:-1] - 1).round(4),
1374
+ [pol2.wage_growth_rates(year=year) for year in window[:-1]],
1375
+ )
1376
+
1377
+ # Test AMT
1378
+ # Check values for 2017 through 2020 are equal.
1379
+ pol2.set_state(year=[2017, 2018, 2019, 2020])
1380
+ for i in (1, 2, 3):
1381
+ np.testing.assert_equal(
1382
+ pol2.AMT_em[0], pol2.AMT_em[i]
1383
+ )
1384
+
1385
+ # check inflation rate applied for rest of window.
1386
+ window = list(range(2020, pol2.end_year + 1))
1387
+ pol2.set_state(year=window)
1388
+ # repeat inflation rates accross matrix so they can be compared to the
1389
+ # rates derived from AMT_em, a 5 * N matrix.
1390
+ exp_rates = [pol2.inflation_rates(year=year) for year in window[:-1]]
1391
+ exp_rates = np.tile([exp_rates], (5, 1)).transpose()
1392
+ np.testing.assert_equal(
1393
+ (pol2.AMT_em[1:] / pol2.AMT_em[:-1] - 1).round(4),
1394
+ exp_rates,
1395
+ )
1396
+
1397
+ # Test II_em
1398
+ # check inflation is not applied.
1399
+ pol2.set_state(year=[2016, 2017])
1400
+ np.testing.assert_equal(
1401
+ pol2.II_em[0], pol2.II_em[1]
1402
+ )
1403
+
1404
+ # check inflation rate is applied.
1405
+ pol2.set_state(year=[2018, 2019])
1406
+ np.testing.assert_equal(
1407
+ (pol2.II_em[1] / pol2.II_em[0] - 1).round(4),
1408
+ pol2.inflation_rates(year=2018),
1409
+ )
1410
+
1411
+ # check inflation rate applied for rest of window.
1412
+ window = list(range(2020, pol2.end_year + 1))
1413
+ pol2.set_state(year=window)
1414
+ np.testing.assert_equal(
1415
+ (pol2.II_em[1:] / pol2.II_em[:-1] - 1).round(4),
1416
+ [pol2.inflation_rates(year=year) for year in window[:-1]],
1417
+ )
1418
+
1419
+
1420
+ def test_adj_CPI_offset_and_index_status():
1421
+ """
1422
+ Test changing parameter_indexing_CPI_offset and another
1423
+ parameter simultaneously.
1424
+ """
1425
+ pol1 = Policy()
1426
+ pol1.implement_reform({
1427
+ "CTC_c-indexed": {2020: True},
1428
+ "parameter_indexing_CPI_offset": {2020: -0.005}},
1429
+ )
1430
+ pol2 = Policy()
1431
+ pol2.adjust(
1432
+ {
1433
+ "parameter_indexing_CPI_offset":
1434
+ [{"year": 2020, "value": -0.005}],
1435
+ "CTC_c-indexed": [{"year": 2020, "value": True}],
1436
+ }
1437
+ )
1438
+ cmp_policy_objs(pol1, pol2)
1439
+
1440
+ # Check no difference prior to 2020
1441
+ pol0 = Policy()
1442
+ pol0.implement_reform({"parameter_indexing_CPI_offset": {2020: 0}})
1443
+ cmp_policy_objs(
1444
+ pol0,
1445
+ pol2,
1446
+ year_range=range(pol2.start_year, 2020 + 1),
1447
+ exclude=["parameter_indexing_CPI_offset"]
1448
+ )
1449
+
1450
+ pol2.set_state(year=[2021, 2022])
1451
+ np.testing.assert_equal(
1452
+ (pol2.CTC_c[1] / pol2.CTC_c[0] - 1).round(4),
1453
+ round(pol0.inflation_rates(year=2021) + (-0.005), 4),
1454
+ )
1455
+
1456
+
1457
+ def test_adj_related_parameters_and_index_status():
1458
+ """
1459
+ Test changing two related parameters simulataneously and
1460
+ one of their indexed statuses.
1461
+ """
1462
+ pol = Policy()
1463
+ pol.adjust(
1464
+ {
1465
+ "II_brk7-indexed": [{"year": 2020, "value": True}],
1466
+ # Update II_brk5 in 2026 to make reform valid after reset.
1467
+ "II_brk5": [{"value": 330000, "MARS": "single", "year": 2026}],
1468
+ "II_brk6": [{"value": 316700, "MARS": "single", "year": 2020}],
1469
+ "II_brk7": [{"value": 445400, "MARS": "single", "year": 2020}],
1470
+ }
1471
+ )
1472
+
1473
+ # Check no difference prior to 2020
1474
+ pol0 = Policy()
1475
+ cmp_policy_objs(
1476
+ pol0,
1477
+ pol,
1478
+ year_range=range(pol.start_year, 2019 + 1),
1479
+ )
1480
+
1481
+ res = (
1482
+ (pol.sel["II_brk6"]["MARS"] == "single")
1483
+ & (pol.sel["II_brk6"]["year"] == 2020)
1484
+ )
1485
+ assert res.isel[0]["value"] == [316700]
1486
+ res = (
1487
+ (pol.sel["II_brk7"]["MARS"] == "single")
1488
+ & (pol.sel["II_brk7"]["year"] == 2020)
1489
+ )
1490
+ assert res.isel[0]["value"] == [445400]
1491
+
1492
+ II_brk7 = pol.to_array("II_brk7", year=[2021, 2022])
1493
+ II_brk7_single = II_brk7[:, 0]
1494
+ np.testing.assert_equal(
1495
+ (II_brk7_single[1] / II_brk7_single[0] - 1).round(4),
1496
+ pol.inflation_rates(year=2021),
1497
+ )
1498
+
1499
+
1500
+ def test_indexed_status_parsing():
1501
+ """
1502
+ Test parsing.
1503
+ """
1504
+ pol1 = Policy()
1505
+ pol1.implement_reform({"EITC_c-indexed": {pol1.start_year: False}})
1506
+ pol2 = Policy()
1507
+ pol2.adjust({"EITC_c-indexed": False})
1508
+ cmp_policy_objs(pol1, pol2)
1509
+
1510
+ with pytest.raises(pt.ValidationError):
1511
+ pol2.adjust({"EITC_c-indexed": 123})
1512
+
1513
+
1514
+ def test_cpi_offset_does_not_affect_wage_indexed_params():
1515
+ """
1516
+ Test adjusting parameter_indexing_CPI_offset does not affect unknown
1517
+ values of wage indexed parameters like SS_Earnings_c.
1518
+ """
1519
+ base_reform = {
1520
+ "parameter_indexing_CPI_offset": {2021: -0.001},
1521
+ "SS_Earnings_c": {2024: 300000},
1522
+ }
1523
+ pol0 = Policy()
1524
+ pol0.implement_reform(base_reform)
1525
+ pol1 = Policy()
1526
+ pol1.implement_reform(base_reform)
1527
+ pol1.implement_reform(dict(base_reform, SS_Earnings_c={2025: 500000}))
1528
+
1529
+ exp_before_2025 = pol0.to_array(
1530
+ "SS_Earnings_c", year=list(range(2021, 2024 + 1))
1531
+ )
1532
+ act_before_2025 = pol1.to_array(
1533
+ "SS_Earnings_c", year=list(range(2021, 2024 + 1))
1534
+ )
1535
+ np.testing.assert_equal(act_before_2025, exp_before_2025)
1536
+
1537
+
1538
+ def test_two_sets_of_tax_brackets():
1539
+ """
1540
+ Test that II_brk? and PT_brk? values are the same under current law.
1541
+ """
1542
+ pol = Policy()
1543
+ brackets = range(1, 7+1)
1544
+ years = range(Policy.JSON_START_YEAR, Policy.LAST_KNOWN_YEAR+1)
1545
+ emsg = ''
1546
+ for year in years:
1547
+ pol.set_year(year)
1548
+ pdata = dict(pol.items())
1549
+ for bnum in brackets:
1550
+ ii_val = pdata[f'II_brk{bnum}']
1551
+ pt_val = pdata[f'PT_brk{bnum}']
1552
+ if not np.allclose(ii_val, pt_val):
1553
+ emsg += f'II_brk{bnum} != PT_brk{bnum} for year {year}\n'
1554
+ emsg += f' II_brk{bnum} is {ii_val}\n'
1555
+ emsg += f' PT_brk{bnum} is {pt_val}\n'
1556
+ if emsg:
1557
+ raise ValueError(emsg)