taxcalc 5.3.0__py3-none-any.whl → 6.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,330 +0,0 @@
1
- """
2
- Compares Tax-Calculator PUF and CPS results with historical information.
3
- """
4
- # CODING-STYLE CHECKS:
5
- # pycodestyle test_compare.py
6
- # pylint --disable=locally-disabled test_compare.py
7
-
8
- import os
9
- import pytest
10
- import numpy as np
11
- from taxcalc.policy import Policy
12
- from taxcalc.records import Records
13
- from taxcalc.calculator import Calculator
14
- from taxcalc.utils import add_income_table_row_variable, SOI_AGI_BINS
15
-
16
-
17
- # 2015 IRS-SOI amounts by AGI category are from "Table 3.3 All Returns: Tax
18
- # Liability, Tax Credits, and Tax Payments by Size of Adjusted Gross Income,
19
- # Tax Year 2015" which is available as a spreadsheet at this URL:
20
- # <https://www.irs.gov/statistics/soi-tax-stats-individual-
21
- # statistical-tables-by-size-of-adjusted-gross-income>
22
- # The IRS-SOI amounts are from 19 rows in the spreadsheet numbered from
23
- # 11 (AGI under one dollar) through 29 (AGI $10M or more).
24
- # Dollar IRS-SOI amounts are expressed in billions of dollars and rounded
25
- # to the nearest one-tenth of a million dollars.
26
- ITAX = {
27
- '0:EITC': {
28
- # Full earned income credit
29
- # in 2015 using the IRS-SOI information described above.
30
- # EITC is column (37) in the Table 3.3 spreadsheet,
31
- # which is the sum of columns (47), (75) and (97).
32
- 'SOI': [0.2104,
33
- 1.1843,
34
- 7.1562,
35
- 16.5927,
36
- 15.8799,
37
- 11.1025,
38
- 7.5150,
39
- 7.4528,
40
- 1.3936,
41
- 0.0375,
42
- 0.0000,
43
- 0.0000,
44
- 0.0000,
45
- 0.0000,
46
- 0.0000,
47
- 0.0000,
48
- 0.0000,
49
- 0.0000,
50
- 0.0000],
51
- 'TC': ['eitc']
52
- },
53
-
54
- '1:FCTC': {
55
- # Full (basic and additional, refundable and nonrefundable) child tax
56
- # credit in 2015 using the IRS-SOI information described above.
57
- # FCTC is sum of columns (13) and (39) in the Table 3.3 spreadsheet.
58
- 'SOI': [0.1301,
59
- 0.0793,
60
- 1.4740,
61
- 4.2580,
62
- 5.2104,
63
- 4.6582,
64
- 4.3166,
65
- 7.2320,
66
- 5.2848,
67
- 9.4151,
68
- 6.4075,
69
- 5.2222,
70
- 0.0018,
71
- 0.0000,
72
- 0.0000,
73
- 0.0000,
74
- 0.0000,
75
- 0.0000,
76
- 0.0000],
77
- 'TC': ['c07220', # FCTC that is nonrefundable
78
- 'c11070'] # FCTC that isrefundable
79
- },
80
-
81
- '2:NIIT': {
82
- # Net investment income tax
83
- # in 2015 using the IRS-SOI information described above.
84
- # NIIT is column (53) in the Table 3.3 spreadsheet.
85
- # NIIT is included in Tax-Calculator individual income tax liability.
86
- 'SOI': [0.0004,
87
- 0.0000,
88
- 0.0000,
89
- 0.0000,
90
- 0.0000,
91
- 0.0000,
92
- 0.0001,
93
- 0.0000,
94
- 0.0000,
95
- 0.0014,
96
- 0.0005,
97
- 0.0213,
98
- 2.6397,
99
- 3.1356,
100
- 1.6715,
101
- 1.0775,
102
- 3.1267,
103
- 2.0949,
104
- 8.2730],
105
- 'TC': ['niit']
106
- },
107
-
108
- '3:ITAX': {
109
- # Total income tax liability
110
- # in 2015 using the IRS-SOI information described above.
111
- # ITAX is column (55) in the Table 3.3 spreadsheet,
112
- # which includes NIIT and is after all credits.
113
- 'SOI': [0.2425,
114
- 0.0409,
115
- 0.3680,
116
- 1.3813,
117
- 3.5238,
118
- 6.1911,
119
- 8.7526,
120
- 25.1677,
121
- 32.5302,
122
- 99.7918,
123
- 105.9015,
124
- 316.3496,
125
- 299.8322,
126
- 154.3888,
127
- 66.3236,
128
- 39.6716,
129
- 101.4885,
130
- 56.3344,
131
- 139.6113],
132
- 'TC': ['iitax']
133
- },
134
-
135
- '4:SETAX': {
136
- # Self-employment tax
137
- # in 2015 using the IRS-SOI information described above.
138
- # SETAX is column (59) in the Table 3.3 spreadsheet,
139
- # which is not part of ITAX but is part of total payroll taxes.
140
- 'SOI': [0.6557,
141
- 0.5554,
142
- 1.8956,
143
- 3.5143,
144
- 2.8228,
145
- 1.9959,
146
- 1.8020,
147
- 3.3598,
148
- 2.8199,
149
- 5.9579,
150
- 5.2751,
151
- 12.1488,
152
- 9.6864,
153
- 3.4864,
154
- 1.1938,
155
- 0.6432,
156
- 1.2527,
157
- 0.4698,
158
- 0.6383],
159
- 'TC': ['setax']
160
- },
161
-
162
- '5:AMTAX': {
163
- # Additional Medicare tax
164
- # in 2015 using the IRS-SOI information described above.
165
- # AMTAX is column (71) in the Table 3.3 spreadsheet,
166
- # which is not part of ITAX but is part of total payroll taxes.
167
- 'SOI': [0.0225,
168
- 0.0003,
169
- 0.0000,
170
- 0.0002,
171
- 0.0002,
172
- 0.0004,
173
- 0.0002,
174
- 0.0041,
175
- 0.0071,
176
- 0.0057,
177
- 0.0026,
178
- 0.0372,
179
- 1.8356,
180
- 2.0214,
181
- 0.8602,
182
- 0.4898,
183
- 1.1730,
184
- 0.5805,
185
- 0.9787],
186
- 'TC': ['ptax_amc']
187
- }
188
- }
189
-
190
-
191
- def comparison(cname, calc, cmpdata, ofile):
192
- """
193
- Write comparison results for cname to ofile.
194
- """
195
- # pylint: disable=too-many-locals
196
- # generate compare table for cvarname
197
- vardf = calc.dataframe(['s006', 'c00100']) # weight and AGI
198
- # add compare variable to vardf
199
- cvar = np.zeros(calc.array_len)
200
- for var in cmpdata[cname]['TC']:
201
- cvar += calc.array(var)
202
- vardf['cvar'] = cvar
203
- # construct AGI table
204
- vardf = add_income_table_row_variable(vardf, 'c00100', SOI_AGI_BINS)
205
- gbydf = vardf.groupby('table_row', as_index=False, observed=True)
206
- # write AGI table with ALL row at bottom of ofile
207
- ofile.write(f"TABLE for {cname.split(':')[1]}\n")
208
- results = '{:23s}\t{:8.3f}\t{:8.3f}\t{:+6.1f}\n'
209
- colhead = f"{'AGIcategory':23s}\t{'T-C':>8s}\t{'SOI':>8s}\t{'%diff':>6s}\n"
210
- ofile.write(colhead)
211
- # pylint: disable=consider-using-f-string
212
- txc_tot = 0.
213
- soi_tot = 0.
214
- idx = 0
215
- for grp_interval, grp in gbydf:
216
- txc = (grp['cvar'] * grp['s006']).sum() * 1e-9
217
- soi = cmpdata[cname]['SOI'][idx]
218
- txc_tot += txc
219
- soi_tot += soi
220
- if soi > 0:
221
- pct_diff = 100. * ((txc / soi) - 1.)
222
- else:
223
- pct_diff = np.nan
224
- glabel = f'[{grp_interval.left:.8g}, {grp_interval.right:.8g})'
225
- ofile.write(results.format(glabel, txc, soi, pct_diff))
226
- idx += 1
227
- pct_diff = 100. * ((txc_tot / soi_tot) - 1.)
228
- ofile.write(results.format('ALL', txc_tot, soi_tot, pct_diff))
229
- # pylint: enable=consider-using-f-string
230
-
231
-
232
- def nonsmall_diffs(linelist1, linelist2, small=0.0):
233
- """
234
- Return True if line lists differ significantly; otherwise return False.
235
- Significant numerical difference means one or more numbers differ (between
236
- linelist1 and linelist2) by more than the specified small amount.
237
- """
238
- # embedded function used only in nonsmall_diffs function
239
- def isfloat(value):
240
- """
241
- Return True if value can be cast to float; otherwise return False.
242
- """
243
- try:
244
- float(value)
245
- return True
246
- except ValueError:
247
- return False
248
- # begin nonsmall_diffs logic
249
- assert isinstance(linelist1, list)
250
- assert isinstance(linelist2, list)
251
- if len(linelist1) != len(linelist2):
252
- return True
253
- assert 0.0 <= small <= 1.0
254
- epsilon = 1e-6
255
- smallamt = small + epsilon
256
- for line1, line2 in zip(linelist1, linelist2):
257
- if line1 == line2:
258
- continue
259
- tokens1 = line1.replace(',', '').split()
260
- tokens2 = line2.replace(',', '').split()
261
- for tok1, tok2 in zip(tokens1, tokens2):
262
- tok1_isfloat = isfloat(tok1)
263
- tok2_isfloat = isfloat(tok2)
264
- if tok1_isfloat and tok2_isfloat:
265
- if abs(float(tok1) - float(tok2)) <= smallamt:
266
- continue
267
- return True
268
- if not tok1_isfloat and not tok2_isfloat:
269
- if tok1 == tok2:
270
- continue
271
- return True
272
- return True
273
- return False
274
-
275
-
276
- def differences(afilename, efilename):
277
- """
278
- Check for differences between results in afilename and efilename files.
279
- """
280
- with open(afilename, 'r', encoding='utf-8') as afile:
281
- actres = afile.read()
282
- with open(efilename, 'r', encoding='utf-8') as efile:
283
- expres = efile.read()
284
- diffs = nonsmall_diffs(actres.splitlines(True),
285
- expres.splitlines(True), 0.0)
286
- if diffs:
287
- afname = os.path.basename(afilename)
288
- efname = os.path.basename(efilename)
289
- msg = 'COMPARE RESULTS DIFFER\n'
290
- msg += '-------------------------------------------------\n'
291
- msg += f'--- NEW RESULTS IN {afname} FILE ---\n'
292
- msg += f'--- if new OK, copy {afname} to ---\n'
293
- msg += f'--- {efname} ---\n'
294
- msg += '--- and rerun test. ---\n'
295
- msg += '-------------------------------------------------\n'
296
- raise ValueError(msg)
297
- os.remove(afilename)
298
-
299
-
300
- @pytest.mark.pre_release
301
- @pytest.mark.requires_pufcsv
302
- @pytest.mark.parametrize('using_puf', [True, False])
303
- def test_itax_compare(tests_path, using_puf, puf_fullsample, cps_fullsample):
304
- """
305
- Conduct income tax comparisons using ITAX data.
306
- """
307
- using_puf_adjust_ratios = True
308
- # generate 2015 estimates by AGI category using Tax-Calculator
309
- if using_puf:
310
- if using_puf_adjust_ratios:
311
- recs = Records(data=puf_fullsample)
312
- else:
313
- recs = Records(data=puf_fullsample, adjust_ratios=None)
314
- else:
315
- recs = Records.cps_constructor(data=cps_fullsample)
316
- calc = Calculator(policy=Policy(), records=recs, verbose=False)
317
- calc.advance_to_year(2015)
318
- calc.calc_all()
319
- # open actual output file
320
- if using_puf:
321
- afilename = os.path.join(tests_path, 'cmpi_puf_actual.txt')
322
- else:
323
- afilename = os.path.join(tests_path, 'cmpi_cps_actual.txt')
324
- with open(afilename, 'w', encoding='utf-8') as afile:
325
- # write compare results to afile
326
- for cname in sorted(ITAX.keys()):
327
- comparison(cname, calc, ITAX, afile)
328
- # check for differences between actual and expect output files
329
- efilename = afilename.replace('actual', 'expect')
330
- differences(afilename, efilename)
@@ -1,334 +0,0 @@
1
- """
2
- Tests of the compatible_data fields in the policy_current_law.json file.
3
-
4
- In order to tap into the parallelization capabilities of py.test, this module
5
- leans heavily on py.tests's `parametrization` method. Once you do so, the
6
- plug-in pytest-xdist is able to run all parametrized functions in parallel
7
- """
8
- # CODING-STYLE CHECKS:
9
- # pycodestyle test_compatible_data.py
10
- # pylint --disable=locally-disabled test_compatible_data.py
11
-
12
- import copy
13
- import pytest
14
- import numpy as np
15
- from taxcalc.policy import Policy
16
- from taxcalc.records import Records
17
- from taxcalc.calculator import Calculator
18
-
19
-
20
- @pytest.fixture(scope='module', name='allparams')
21
- def fixture_allparams():
22
- """
23
- Return metadata for current-law policy parameters.
24
- """
25
- clp = Policy()
26
- return clp.metadata()
27
-
28
-
29
- def test_compatible_data_presence(allparams):
30
- """
31
- Test that every parameter in the policy_current_law.json file
32
- has a compatible_data field that is a dictionary.
33
- """
34
- compatible_data_keys_set = set(['puf', 'cps'])
35
-
36
- # Nested function used only in test_compatible_data_presence
37
- def valid_compatible_data(compatible_data):
38
- """
39
- Return True if compatible_data is a valid dictionary;
40
- otherwise return False
41
- """
42
- if not isinstance(compatible_data, dict):
43
- return False
44
- if set(compatible_data.keys()) != compatible_data_keys_set:
45
- return False
46
- for key in compatible_data:
47
- boolean = (compatible_data[key] is True or
48
- compatible_data[key] is False)
49
- if not boolean:
50
- return False
51
- return True
52
-
53
- # Main logic of test_compatible_data_presence function
54
- problem_pnames = []
55
- for pname in allparams:
56
- if 'compatible_data' in allparams[pname]:
57
- compatible_data = allparams[pname]['compatible_data']
58
- else:
59
- compatible_data = None
60
- if not valid_compatible_data(compatible_data):
61
- problem_pnames.append(pname)
62
- if problem_pnames:
63
- msg = '{} has no or invalid compatible_data field'
64
- for pname in problem_pnames:
65
- print(msg.format(pname))
66
- assert False, 'ERROR: list of problem_pnames is above'
67
-
68
-
69
- XX_YEAR = 2019
70
- TEST_YEAR = 2020
71
-
72
-
73
- @pytest.fixture(scope='module', name='reform_xx')
74
- def fixture_reform_xx():
75
- """
76
- Fixture for reform dictionary where reform starts before TEST_YEAR.
77
-
78
- The provisions in the baseline reform, designated in _reform_xx,
79
- are chosen to activate parameters that are inactive under current law.
80
- For example a phaseout rate for a new credit is inactive if the credit's
81
- amount is set to zero under current law. In order to activate the phaseout
82
- rate, the credit amount should be set above zero. The provisions interact
83
- with each other: you may acidentally deactivate one parameter
84
- by introducing a provision to activate another. If you find that a pair of
85
- parameters are impossible test jointly, add one to the local variable
86
- `exempt_from_testing` in `test_compatible_data()` as a last resort.
87
- """
88
- assert XX_YEAR < TEST_YEAR
89
-
90
- # Set baseline to activate parameters that are inactive under current law.
91
- _reform_xx = {
92
- XX_YEAR: {
93
- 'FST_AGI_trt': 0.5,
94
- 'CTC_new_rt': 0.5,
95
- 'CTC_new_c': 5000,
96
- 'CTC_new_prt': 0.1,
97
- 'CTC_new_refund_limited': True,
98
- 'CTC_new_refund_limit_payroll_rt': 1,
99
- 'ACTC_ChildNum': 1,
100
- 'UBI_u18': 1000,
101
- 'UBI_1820': 1000,
102
- 'UBI_21': 1000,
103
- 'II_credit_prt': 0.1,
104
- 'II_credit': [100, 100, 100, 100, 100],
105
- 'CG_brk3': [1000000, 1000000, 1000000, 1000000, 1000000],
106
- 'ALD_Dependents_Child_c': 1000,
107
- 'II_credit_nr': [1000, 1000, 1000, 1000, 1000],
108
- 'II_credit_nr_prt': 0.1,
109
- 'AMT_CG_brk3': [500000, 500000, 500000, 500000, 500000],
110
- 'AGI_surtax_thd': [1000000, 1000000, 1000000, 1000000, 1000000],
111
- 'AGI_surtax_trt': 0.5,
112
- 'II_brk7': [1000000, 1000000, 1000000, 1000000, 1000000],
113
- 'II_em': 1000,
114
- 'ID_Casualty_hc': 0.1,
115
- 'ID_Miscellaneous_hc': 0.1,
116
- 'ID_prt': 0.03,
117
- 'ID_crt': 0.8,
118
- 'CR_Charity_rt': 0.4,
119
- 'CR_Charity_f': [5000, 5000, 5000, 5000, 5000],
120
- 'CR_Charity_frt': 0.5,
121
- 'CR_SchR_hc': 0.5
122
- }
123
- }
124
- return _reform_xx
125
-
126
-
127
- @pytest.fixture(scope='module', name='sorted_param_names')
128
- def fixture_sorted_param_names(allparams):
129
- """
130
- Fixture for storing a sorted parameter list
131
- """
132
- return sorted(list(allparams.keys()))
133
-
134
-
135
- NPARAMS = 219 # hard-code NPARAMS to len(allparams)
136
- BATCHSIZE = 10
137
- BATCHES = int(np.floor(NPARAMS / BATCHSIZE)) + 1
138
-
139
-
140
- @pytest.fixture(scope='module', name='allparams_batch',
141
- params=list(range(0, BATCHES)))
142
- def fixture_allparams_batch(request, allparams, sorted_param_names):
143
- """
144
- Fixture for grouping Tax-Calculator parameters
145
-
146
- Experiments indicated that there is some overhead when you run
147
- `test_compatible_data` on each parameter individually. Suppose it takes X
148
- amount of time to set up the test data for `test_compatible_data` and Y
149
- amount of time to run `test_compatible_data` on each parameter wihtout
150
- parallelization. Then, if there is no overhead from parallelization, you
151
- would expect it to take Y + (X / NUMBER_WORKERS) to run these tests in
152
- parallel. Note that setup data is only created once if you set the
153
- fixture scope to 'module'. However, experiments indicated that there was
154
- so much overhead that the tests weren't that much faster in parallel than
155
- if they were run sequentially.
156
-
157
- I found that running the parameters in batches decreased the amount of
158
- overhead. Further, there was an optimal batch size that I found through
159
- trial and error. On my local machine, this was something like 10
160
- parameters. Others may find a different optimal batch size on their
161
- machines. Further, if the number of parameters changes, the optimal
162
- batch size could change, too.
163
-
164
- Math for partitioning the parameters:
165
-
166
- Suppose we have N parameters and choose batch size n. Then, we have
167
- B batches where B equals floor(N / n) + 1.
168
-
169
- Case 1: N % n = 0
170
- Then we have:
171
- idx_min = {i * b, i = 0, 1, 2, 3, ..., B - 1} and
172
- idx_max = {min((i + 1) * b, N), i = 0, 1, 2, 3, ..., B - 1}
173
-
174
- So, if i equals 0, the batch contains the first b - 1 parameters.
175
- Then, if i equals B, then idx_min is n * (B - 1) = N and idx_max is N and
176
- thus, the last batch is empty.
177
-
178
- Case 2: N % n = r > 0
179
- Then, everything is the same as case 1, except for the final batch.
180
- In the final batch, idx_min = b * (B - 1) = b * floor(N / n) < N, and
181
- idx_max is N. So, we our final batch size is
182
- idx_max - idx_min = N - b * B = r.
183
-
184
- returns: dictionary of size, BATCHSIZE, or for the final batch,
185
- either an empty dictionary or dictionary of size NPARAMS mod BATCHSIZE
186
- """
187
- idx = request.param
188
- idx_start = idx * BATCHSIZE
189
- idx_end = min((idx + 1) * BATCHSIZE, NPARAMS)
190
- pnames = sorted_param_names[idx_start: idx_end]
191
- return {pname: allparams[pname] for pname in pnames}
192
-
193
-
194
- @pytest.fixture(scope='module', name='tc_objs',
195
- params=[True, False])
196
- def fixture_tc_objs(request, reform_xx, puf_subsample, cps_subsample):
197
- """
198
- Fixture for creating Tax-Calculator objects that use the PUF and
199
- use the CPS (called only twice: once for PUF and once for CPS)
200
- """
201
- puftest = request.param
202
- p_xx = Policy()
203
- p_xx.implement_reform(reform_xx, raise_errors=False)
204
- if puftest:
205
- rec_xx = Records(data=puf_subsample)
206
- else:
207
- rec_xx = Records.cps_constructor(data=cps_subsample)
208
- c_xx = Calculator(policy=p_xx, records=rec_xx)
209
- c_xx.advance_to_year(TEST_YEAR)
210
- c_xx.calc_all()
211
- return rec_xx, c_xx, puftest
212
-
213
-
214
- @pytest.mark.skip
215
- @pytest.mark.pre_release
216
- @pytest.mark.compatible_data
217
- @pytest.mark.requires_pufcsv
218
- def test_compatible_data(cps_subsample, puf_subsample,
219
- allparams, reform_xx,
220
- tc_objs, allparams_batch):
221
- """
222
- Test that the compatible_data attribute in policy_current_law.json
223
- is accurate by implementing the min and max values of each parameter
224
- as reforms and ensuring that revenue differs from baseline when for
225
- at least one of these reforms when using datasets marked compatible
226
- and does not differ when using datasets marked as incompatible.
227
- """
228
- # pylint: disable=too-many-arguments,too-many-positional-arguments
229
- # pylint: disable=too-many-statements,too-many-branches,too-many-locals
230
-
231
- # Check NPARAMS value
232
- assert NPARAMS == len(allparams)
233
-
234
- # Get taxcalc objects from tc_objs fixture
235
- rec_xx, c_xx, puftest = tc_objs
236
-
237
- # These parameters are exempt because they are not active under
238
- # current law and activating them would deactivate other parameters,
239
- # or if it is difficult to devise a test for them.
240
- exempt_from_testing = [
241
- 'CG_ec', 'CG_reinvest_ec_rt',
242
- 'II_prt', 'ID_prt', 'ID_crt',
243
- 'CR_SchR_hc', 'ACTC_ChildNum'
244
- ]
245
-
246
- # Loop through the parameters in allparams_batch
247
- errmsg = 'ERROR: {} {}\n'
248
- errors = ''
249
- for pname in allparams_batch:
250
- param = allparams_batch[pname]
251
- max_listed = param['valid_values']['max']
252
- # handle links to other params or self
253
- if isinstance(max_listed, str):
254
- if isinstance(allparams[max_listed]['value'][0], list):
255
- max_val = allparams[max_listed]['value'][0]
256
- else:
257
- max_val = float(allparams[max_listed]['value'][0])
258
- else:
259
- if isinstance(param['value'][0], list):
260
- max_val = [max_listed] * len(param['value'][0])
261
- else:
262
- max_val = max_listed
263
- min_listed = param['valid_values']['min']
264
- if isinstance(min_listed, str):
265
- if isinstance(allparams[min_listed]['value'][0], list):
266
- min_val = allparams[min_listed]['value'][0]
267
- else:
268
- min_val = float(allparams[min_listed]['value'][0])
269
- else:
270
- if isinstance(param['value'][0], list):
271
- min_val = [min_listed] * len(param['value'][0])
272
- else:
273
- min_val = min_listed
274
- # create reform dictionaries
275
- max_reform = copy.deepcopy(reform_xx)
276
- min_reform = copy.deepcopy(reform_xx)
277
- max_reform[XX_YEAR][str(pname)] = [max_val]
278
- min_reform[XX_YEAR][str(pname)] = [min_val]
279
- # assess whether max reform changes results
280
- if puftest:
281
- rec_yy = Records(data=puf_subsample)
282
- else:
283
- rec_yy = Records.cps_constructor(data=cps_subsample)
284
- p_yy = Policy()
285
- p_yy.implement_reform(max_reform, raise_errors=False)
286
- c_yy = Calculator(policy=p_yy, records=rec_yy, verbose=False)
287
- c_yy.advance_to_year(TEST_YEAR)
288
- c_yy.calc_all()
289
- if pname.startswith('BEN') and pname.endswith('_repeal'):
290
- max_reform_change = (
291
- c_yy.weighted_total('benefit_cost_total') -
292
- c_xx.weighted_total('benefit_cost_total')
293
- )
294
- else:
295
- max_reform_change = (
296
- c_yy.weighted_total('combined') -
297
- c_xx.weighted_total('combined')
298
- )
299
- min_reform_change = 0
300
- # assess whether min reform changes results, if max reform did not
301
- if max_reform_change == 0:
302
- p_yy = Policy()
303
- p_yy.implement_reform(min_reform, raise_errors=False)
304
- c_yy = Calculator(policy=p_yy, records=rec_xx)
305
- c_yy.advance_to_year(TEST_YEAR)
306
- c_yy.calc_all()
307
- if pname.startswith('BEN') and pname.endswith('_repeal'):
308
- min_reform_change = (
309
- c_yy.weighted_total('benefit_cost_total') -
310
- c_xx.weighted_total('benefit_cost_total')
311
- )
312
- else:
313
- min_reform_change = (
314
- c_yy.weighted_total('combined') -
315
- c_xx.weighted_total('combined')
316
- )
317
- if min_reform_change == 0 and pname not in exempt_from_testing:
318
- if puftest:
319
- if param['compatible_data']['puf'] is True:
320
- errors += errmsg.format(pname, 'is not True for puf')
321
- else:
322
- if param['compatible_data']['cps'] is True:
323
- errors += errmsg.format(pname, 'is not True for cps')
324
- if max_reform_change != 0 or min_reform_change != 0:
325
- if puftest:
326
- if param['compatible_data']['puf'] is False:
327
- errors += errmsg.format(pname, 'is not False for puf')
328
- else:
329
- if param['compatible_data']['cps'] is False:
330
- errors += errmsg.format(pname, 'is not False for cps')
331
- # test failure if any errors
332
- if errors:
333
- print(errors)
334
- assert False, 'ERROR: compatible_data is invalid; see errors above'