taxcalc 5.2.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.
Files changed (46) hide show
  1. taxcalc/__init__.py +3 -3
  2. taxcalc/calcfunctions.py +2 -2
  3. taxcalc/calculator.py +4 -4
  4. taxcalc/cli/tc.py +16 -19
  5. taxcalc/data.py +2 -3
  6. taxcalc/decorators.py +9 -8
  7. taxcalc/growfactors.py +2 -1
  8. taxcalc/policy.py +6 -23
  9. taxcalc/policy_current_law.json +31 -631
  10. taxcalc/records.py +78 -82
  11. taxcalc/records_variables.json +106 -106
  12. taxcalc/reforms/ARPA.out.csv +9 -9
  13. taxcalc/taxcalcio.py +101 -77
  14. taxcalc/tests/conftest.py +20 -15
  15. taxcalc/tests/puf_var_correl_coeffs_2016.csv +24 -24
  16. taxcalc/tests/puf_var_wght_means_by_year.csv +11 -11
  17. taxcalc/tests/pufcsv_agg_expect.csv +20 -20
  18. taxcalc/tests/pufcsv_mtr_expect.txt +21 -21
  19. taxcalc/tests/reforms.json +3 -1
  20. taxcalc/tests/reforms_expect.csv +54 -54
  21. taxcalc/tests/test_4package.py +8 -9
  22. taxcalc/tests/test_calculator.py +55 -18
  23. taxcalc/tests/test_consumption.py +2 -2
  24. taxcalc/tests/test_cpscsv.py +2 -24
  25. taxcalc/tests/test_data.py +11 -3
  26. taxcalc/tests/test_decorators.py +57 -52
  27. taxcalc/tests/test_growdiff.py +2 -2
  28. taxcalc/tests/test_parameters.py +101 -53
  29. taxcalc/tests/test_policy.py +154 -154
  30. taxcalc/tests/test_records.py +144 -9
  31. taxcalc/tests/test_reforms.py +104 -104
  32. taxcalc/tests/test_taxcalcio.py +13 -62
  33. taxcalc/utils.py +3 -3
  34. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/METADATA +3 -6
  35. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/RECORD +39 -46
  36. taxcalc/puf_ratios.csv +0 -26
  37. taxcalc/puf_weights.csv.gz +0 -0
  38. taxcalc/reforms/clp.out.csv +0 -10
  39. taxcalc/tests/test_compare.py +0 -330
  40. taxcalc/tests/test_compatible_data.py +0 -334
  41. taxcalc/tests/test_puf_var_stats.py +0 -194
  42. taxcalc/tests/test_pufcsv.py +0 -328
  43. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/WHEEL +0 -0
  44. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/entry_points.txt +0 -0
  45. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/licenses/LICENSE +0 -0
  46. {taxcalc-5.2.0.dist-info → taxcalc-6.0.0.dist-info}/top_level.txt +0 -0
@@ -29,10 +29,80 @@ def test_incorrect_records_instantiation(cps_subsample, cps_fullsample):
29
29
  _ = Records(data=cps_subsample, gfactors=None, weights=None,
30
30
  adjust_ratios=[])
31
31
  # test error raise when num of records is greater than num of weights
32
- wghts_path = os.path.join(Records.CODE_PATH, Records.PUF_WEIGHTS_FILENAME)
33
- puf_wghts = pd.read_csv(wghts_path)
32
+ cps_weights_path = os.path.join(Records.CODE_PATH, 'cps_weights.csv.gz')
33
+ weights = pd.read_csv(cps_weights_path)
34
+ some_wghts = weights[:100]
34
35
  with pytest.raises(ValueError):
35
- _ = Records(data=cps_fullsample, weights=puf_wghts, start_year=2020)
36
+ _ = Records(data=cps_fullsample, weights=some_wghts, start_year=2020)
37
+
38
+
39
+ def test_invalid_variable_values_1(cps_subsample):
40
+ """Test docstring"""
41
+ dta = cps_subsample.copy()
42
+ dta['PT_SSTB_income'] = 2
43
+ with pytest.raises(ValueError):
44
+ _ = Records(data=dta, start_year=2000)
45
+ dta['e01700'] = 1000
46
+ with pytest.raises(ValueError):
47
+ _ = Records(data=dta, start_year=2000)
48
+ dta['e00650'] = 1000
49
+ with pytest.raises(ValueError):
50
+ _ = Records(data=dta, start_year=2000)
51
+ dta['k1bx14s'] = 1000
52
+ with pytest.raises(ValueError):
53
+ _ = Records(data=dta, start_year=2000)
54
+ dta['e02100'] = dta['e02100p'] + dta['e02100s'] + 1000
55
+ with pytest.raises(ValueError):
56
+ _ = Records(data=dta, start_year=2000)
57
+ dta['e00900'] = dta['e00900p'] + dta['e00900s'] + 1000
58
+ with pytest.raises(ValueError):
59
+ _ = Records(data=dta, start_year=2000)
60
+ dta['e00200'] = dta['e00200p'] + dta['e00200s'] + 1000
61
+ with pytest.raises(ValueError):
62
+ _ = Records(data=dta, start_year=2000)
63
+ dta['EIC'] = 4
64
+ with pytest.raises(ValueError):
65
+ _ = Records(data=dta, start_year=2000)
66
+ dta['MARS'] = 0
67
+ with pytest.raises(ValueError):
68
+ _ = Records(data=dta, start_year=2000)
69
+
70
+
71
+ def test_invalid_variable_values_2():
72
+ """Test docstring"""
73
+ dta = pd.DataFrame(
74
+ {
75
+ 'RECID': [1],
76
+ 'MARS': [1],
77
+ 'e00200p': [8e4],
78
+ 'e00200s': [1e4],
79
+ 'e00200': [9e4],
80
+ }
81
+ )
82
+ with pytest.raises(ValueError):
83
+ _ = Records(data=dta, start_year=2000)
84
+ dta = pd.DataFrame(
85
+ {
86
+ 'RECID': [1],
87
+ 'MARS': [1],
88
+ 'e00900p': [8e4],
89
+ 'e00900s': [1e4],
90
+ 'e00900': [9e4],
91
+ }
92
+ )
93
+ with pytest.raises(ValueError):
94
+ _ = Records(data=dta, start_year=2000)
95
+ dta = pd.DataFrame(
96
+ {
97
+ 'RECID': [1],
98
+ 'MARS': [1],
99
+ 'e02100p': [8e4],
100
+ 'e02100s': [1e4],
101
+ 'e02100': [9e4],
102
+ }
103
+ )
104
+ with pytest.raises(ValueError):
105
+ _ = Records(data=dta, start_year=2000)
36
106
 
37
107
 
38
108
  def test_correct_records_instantiation(cps_subsample):
@@ -45,16 +115,15 @@ def test_correct_records_instantiation(cps_subsample):
45
115
  rec1.increment_year()
46
116
  sum_e00200_in_cps_year_plus_one = getattr(rec1, 'e00200').sum()
47
117
  assert sum_e00200_in_cps_year_plus_one == sum_e00200_in_cps_year
48
- wghts_path = os.path.join(Records.CODE_PATH, Records.CPS_WEIGHTS_FILENAME)
118
+ wghts_path = os.path.join(Records.CODE_PATH, 'cps_weights.csv.gz')
49
119
  wghts_df = pd.read_csv(wghts_path)
50
- ratios_path = os.path.join(Records.CODE_PATH, Records.PUF_RATIOS_FILENAME)
51
- ratios_df = pd.read_csv(ratios_path, index_col=0).transpose()
52
120
  rec2 = Records(data=cps_subsample,
53
121
  start_year=Records.CPSCSV_YEAR,
54
122
  gfactors=GrowFactors(),
55
123
  weights=wghts_df,
56
- adjust_ratios=ratios_df,
57
- exact_calculations=False)
124
+ adjust_ratios=None,
125
+ exact_calculations=False,
126
+ weights_scale=0.01)
58
127
  assert rec2
59
128
  assert np.all(getattr(rec2, 'MARS') != 0)
60
129
  assert getattr(rec2, 'current_year') == getattr(rec2, 'data_year')
@@ -66,7 +135,7 @@ def test_read_cps_data(cps_fullsample):
66
135
  assert data.equals(cps_fullsample)
67
136
 
68
137
 
69
- @pytest.mark.parametrize("csv", [
138
+ @pytest.mark.parametrize('csv', [
70
139
  (
71
140
  'RECID,MARS,e00200,e00200p,e00200s\n'
72
141
  '1, 2, 200000, 200000, 0.03\n'
@@ -134,21 +203,25 @@ def test_read_data(csv):
134
203
  def test_for_duplicate_names():
135
204
  """Test docstring"""
136
205
  records_varinfo = Records(data=None)
206
+ num_vars = 0
137
207
  varnames = set()
138
208
  for varname in records_varinfo.USABLE_READ_VARS:
139
209
  assert varname not in varnames
140
210
  varnames.add(varname)
141
211
  assert varname not in records_varinfo.CALCULATED_VARS
212
+ num_vars += len(varnames)
142
213
  varnames = set()
143
214
  for varname in records_varinfo.CALCULATED_VARS:
144
215
  assert varname not in varnames
145
216
  varnames.add(varname)
146
217
  assert varname not in records_varinfo.USABLE_READ_VARS
218
+ num_vars += len(varnames)
147
219
  varnames = set()
148
220
  for varname in records_varinfo.INTEGER_READ_VARS:
149
221
  assert varname not in varnames
150
222
  varnames.add(varname)
151
223
  assert varname in records_varinfo.USABLE_READ_VARS
224
+ assert num_vars == 212 # number of vars in records_variables.json
152
225
 
153
226
 
154
227
  def test_records_variables_content(tests_path):
@@ -245,3 +318,65 @@ def test_csv_input_vars_md_contents(tests_path):
245
318
  for var in valid_less_civ:
246
319
  msg += f'VARIABLE= {var}\n' # pylint: disable=consider-using-join
247
320
  raise ValueError(msg)
321
+
322
+
323
+ def test_cps_availability(tests_path, cps_data_path):
324
+ """
325
+ Cross-check records_variables.json data with variables in cps.csv file.
326
+ """
327
+ # make set of variable names that are in the cps.csv file
328
+ cpsdf = pd.read_csv(cps_data_path)
329
+ cpsvars = set(sorted(list(cpsdf)))
330
+ # make set of variable names that are marked as cps available in r_v.json
331
+ rvpath = os.path.join(tests_path, '..', 'records_variables.json')
332
+ with open(rvpath, 'r', encoding='utf-8') as rvfile:
333
+ rvdict = json.load(rvfile)
334
+ recvars = set()
335
+ for vname, vdict in rvdict['read'].items():
336
+ if 'taxdata_cps' in vdict.get('availability', ''):
337
+ recvars.add(vname)
338
+ # check that cpsvars and recvars sets are the same
339
+ assert (cpsvars - recvars) == set()
340
+ assert (recvars - cpsvars) == set()
341
+
342
+
343
+ @pytest.mark.requires_puf
344
+ def test_puf_availability(tests_path, puf_data_path):
345
+ """
346
+ Cross-check records_variables.json data with variables in puf.csv file
347
+ """
348
+ # make set of variable names that are in the puf.csv file
349
+ pufdf = pd.read_csv(puf_data_path)
350
+ pufvars = set(sorted(list(pufdf)))
351
+ # make set of variable names that are marked as puf available in r_v.json
352
+ rvpath = os.path.join(tests_path, '..', 'records_variables.json')
353
+ with open(rvpath, 'r', encoding='utf-8') as rvfile:
354
+ rvdict = json.load(rvfile)
355
+ recvars = set()
356
+ for vname, vdict in rvdict['read'].items():
357
+ if 'taxdata_puf' in vdict.get('availability', ''):
358
+ recvars.add(vname)
359
+ # check that pufvars and recvars sets are the same
360
+ assert (pufvars - recvars) == set()
361
+ assert (recvars - pufvars) == set()
362
+
363
+
364
+ @pytest.mark.requires_tmd
365
+ def test_tmd_availability(tests_path, tmd_data_path):
366
+ """
367
+ Cross-check records_variables.json data with variables in tmd.csv file
368
+ """
369
+ # make set of variable names that are in the tmd.csv file
370
+ tmddf = pd.read_csv(tmd_data_path)
371
+ tmdvars = set(sorted(list(tmddf)))
372
+ # make set of variable names that are marked as tmd available in r_v.json
373
+ rvpath = os.path.join(tests_path, '..', 'records_variables.json')
374
+ with open(rvpath, 'r', encoding='utf-8') as rvfile:
375
+ rvdict = json.load(rvfile)
376
+ recvars = set()
377
+ for vname, vdict in rvdict['read'].items():
378
+ if 'taxmicrodata_tmd' in vdict.get('availability', ''):
379
+ recvars.add(vname)
380
+ # check that tmdvars and recvars sets are the same
381
+ assert (tmdvars - recvars) == set()
382
+ assert (recvars - tmdvars) == set()
@@ -73,8 +73,19 @@ def test_2017_law_reform(tests_path):
73
73
  assert act == exp, f'{name} a={act} != e={exp}'
74
74
 
75
75
 
76
+ def _apply_reform(policy, reform_path):
77
+ """
78
+ Helper function to apply a reform and assert no errors.
79
+ """
80
+ with open(reform_path, 'r', encoding='utf-8') as rfile:
81
+ rtext = rfile.read()
82
+ policy.implement_reform(Policy.read_json_reform(rtext))
83
+ assert not policy.parameter_errors
84
+ assert not policy.errors
85
+
86
+
76
87
  @pytest.mark.rtr
77
- @pytest.mark.parametrize('fyear', [2019, 2020, 2021, 2022, 2023])
88
+ @pytest.mark.parametrize('fyear', [2019, 2020, 2021, 2022, 2023, 2024, 2025])
78
89
  def test_round_trip_reforms(fyear, tests_path):
79
90
  """
80
91
  Check that current-law policy has the same policy parameter values in
@@ -83,7 +94,7 @@ def test_round_trip_reforms(fyear, tests_path):
83
94
  reforms that represents new tax legislation since 2017.
84
95
  This test checks that the future-year parameter values for
85
96
  current-law policy (which incorporates recent legislation such as
86
- the TCJA, CARES Act, and ARPA) are the same as future-year
97
+ the TCJA, CARES Act, ARPA, and OBBBA) are the same as future-year
87
98
  parameter values for the compound round-trip reform.
88
99
  Doing this check ensures that the 2017_law.json
89
100
  and subsequent reform files that represent recent legislation are
@@ -97,48 +108,21 @@ def test_round_trip_reforms(fyear, tests_path):
97
108
  clp_mdata = dict(clp_pol.items())
98
109
  # create rtr metadata dictionary for round-trip reform in fyear
99
110
  rtr_pol = Policy()
100
- # Revert to 2017 law
101
- reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
102
- with open(reform_file, 'r', encoding='utf-8') as rfile:
103
- rtext = rfile.read()
104
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
105
- assert not rtr_pol.parameter_errors
106
- assert not rtr_pol.errors
107
- # Layer on TCJA
108
- reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json')
109
- with open(reform_file, 'r', encoding='utf-8') as rfile:
110
- rtext = rfile.read()
111
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
112
- assert not rtr_pol.parameter_errors
113
- assert not rtr_pol.errors
114
- # Layer on the CARES Act
115
- reform_file = os.path.join(tests_path, '..', 'reforms', 'CARES.json')
116
- with open(reform_file, 'r', encoding='utf-8') as rfile:
117
- rtext = rfile.read()
118
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
119
- # Layer on the Consolidated Appropriations Act of 2021
120
- reform_file = os.path.join(
121
- tests_path, '..', 'reforms', 'ConsolidatedAppropriationsAct2021.json'
122
- )
123
- with open(reform_file, 'r', encoding='utf-8') as rfile:
124
- rtext = rfile.read()
125
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
126
- assert not rtr_pol.parameter_errors
127
- assert not rtr_pol.errors
128
- # Layer on ARPA
129
- reform_file = os.path.join(tests_path, '..', 'reforms', 'ARPA.json')
130
- with open(reform_file, 'r', encoding='utf-8') as rfile:
131
- rtext = rfile.read()
132
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
133
- assert not rtr_pol.parameter_errors
134
- assert not rtr_pol.errors
135
- # Layer on rounding from IRS through Policy.LAST_KNOWN_YEAR
136
- reform_file = os.path.join(tests_path, '..', 'reforms', 'rounding.json')
137
- with open(reform_file, 'r', encoding='utf-8') as rfile:
138
- rtext = rfile.read()
139
- rtr_pol.implement_reform(Policy.read_json_reform(rtext))
140
- assert not rtr_pol.parameter_errors
141
- assert not rtr_pol.errors
111
+
112
+ reform_files_to_apply = [
113
+ '2017_law.json',
114
+ 'TCJA.json',
115
+ 'CARES.json',
116
+ 'ConsolidatedAppropriationsAct2021.json',
117
+ 'ARPA.json',
118
+ 'OBBBA.json',
119
+ 'rounding.json'
120
+ ]
121
+ for reform_filename in reform_files_to_apply:
122
+ reform_file_path = os.path.join(tests_path, '..',
123
+ 'reforms', reform_filename)
124
+ _apply_reform(rtr_pol, reform_file_path)
125
+
142
126
  rtr_pol.set_year(fyear)
143
127
  rtr_mdata = dict(rtr_pol.items())
144
128
  # compare fyear policy parameter values
@@ -158,7 +142,7 @@ def test_round_trip_reforms(fyear, tests_path):
158
142
  clp_val = clp_mdata[pname]
159
143
  if not np.allclose(rtr_val, clp_val):
160
144
  fail_params.append(pname)
161
- msg += '\n {pname} in {fyear} : rtr={rtr_val} clp={clp_val}'
145
+ msg += f'\n {pname} in {fyear} : rtr={rtr_val} clp={clp_val}'
162
146
  if fail_dump:
163
147
  rtr_fails.write(f'{pname} {fyear} {rtr_val}\n')
164
148
  clp_fails.write(f'{pname} {fyear} {clp_val}\n')
@@ -169,8 +153,23 @@ def test_round_trip_reforms(fyear, tests_path):
169
153
  raise ValueError(msg)
170
154
 
171
155
 
156
+ REFORM_DIR = os.path.join(os.path.dirname(__file__), '..', 'reforms')
157
+ REFORM_FILES = glob.glob(os.path.join(REFORM_DIR, '*.json'))
158
+ REFORM_YEARS = {
159
+ 'ARPA.json': 2022,
160
+ 'ext.json': 2026,
161
+ 'NoOBBBA.json': 2026,
162
+ 'OBBBA.json': 2026,
163
+ }
164
+
165
+
172
166
  @pytest.mark.reforms
173
- def test_reform_json_and_output(tests_path):
167
+ @pytest.mark.parametrize(
168
+ 'reform_file,tax_year',
169
+ [(os.path.basename(f), REFORM_YEARS.get(os.path.basename(f), 2020))
170
+ for f in REFORM_FILES],
171
+ )
172
+ def test_reform_json_and_output(reform_file, tax_year, tests_path):
174
173
  """
175
174
  Check that each JSON reform file can be converted into a reform dictionary
176
175
  that can then be passed to the Policy class implement_reform method that
@@ -213,7 +212,6 @@ def test_reform_json_and_output(tests_path):
213
212
  return not diffs
214
213
 
215
214
  # specify Records object containing cases data
216
- tax_year = 2020
217
215
  cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv')
218
216
  cases = Records(data=cases_path,
219
217
  start_year=tax_year, # set raw input data year
@@ -226,9 +224,10 @@ def test_reform_json_and_output(tests_path):
226
224
  calc = Calculator(policy=Policy(), records=cases, verbose=False)
227
225
  calc.advance_to_year(tax_year)
228
226
  calc.calc_all()
229
- res_path = cases_path.replace('cases.csv', 'clp.res.csv')
227
+ clp_base = cases_path.replace('cases.csv', f'clp-{tax_year}')
228
+ res_path = clp_base + '.res.csv'
230
229
  write_res_file(calc, res_path)
231
- if res_and_out_are_same(res_path.replace('.res.csv', '')):
230
+ if res_and_out_are_same(clp_base):
232
231
  os.remove(res_path)
233
232
  else:
234
233
  failures.append(res_path)
@@ -237,33 +236,29 @@ def test_reform_json_and_output(tests_path):
237
236
  pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json')
238
237
  pre_tcja = Policy.read_json_reform(pre_tcja_jrf)
239
238
  # check reform file contents and reform results for each reform
240
- reforms_path = os.path.join(tests_path, '..', 'reforms', '*.json')
241
- json_reform_files = glob.glob(reforms_path)
242
- for jrf in json_reform_files:
243
- if jrf.endswith('ext.json'):
244
- continue # skip ext.json, which is tested below in test_ext_reform
245
- # determine reform's baseline by reading contents of jrf
246
- with open(jrf, 'r', encoding='utf-8') as rfile:
247
- jrf_text = rfile.read()
248
- pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text
249
- # implement the reform relative to its baseline
250
- reform = Policy.read_json_reform(jrf_text)
251
- pol = Policy() # current-law policy
252
- if pre_tcja_baseline:
253
- pol.implement_reform(pre_tcja)
254
- assert not pol.parameter_errors
255
- pol.implement_reform(reform)
239
+ jrf = os.path.join(tests_path, '..', 'reforms', reform_file)
240
+ # determine reform's baseline by reading contents of jrf
241
+ with open(jrf, 'r', encoding='utf-8') 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)
256
249
  assert not pol.parameter_errors
257
- calc = Calculator(policy=pol, records=cases, verbose=False)
258
- calc.advance_to_year(tax_year)
259
- calc.calc_all()
260
- res_path = jrf.replace('.json', '.res.csv')
261
- write_res_file(calc, res_path)
262
- if res_and_out_are_same(res_path.replace('.res.csv', '')):
263
- os.remove(res_path)
264
- else:
265
- failures.append(res_path)
266
- del calc
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
267
262
  if failures:
268
263
  msg = 'Following reforms have res-vs-out differences:\n'
269
264
  for ref in failures:
@@ -271,12 +266,12 @@ def test_reform_json_and_output(tests_path):
271
266
  raise ValueError(msg)
272
267
 
273
268
 
274
- def reform_results(rid, reform_dict, puf_data, reform_2017_law):
269
+ def reform_results(rid, reform_dict, cps_data, reform_2017_law):
275
270
  """
276
271
  Return actual results of the reform specified by rid and reform_dict.
277
272
  """
278
273
  # pylint: disable=too-many-locals
279
- rec = Records(data=puf_data)
274
+ rec = Records.cps_constructor(data=cps_data)
280
275
  # create baseline Calculator object, calc1
281
276
  pol = Policy()
282
277
  if reform_dict['baseline'] == '2017_law.json':
@@ -341,49 +336,54 @@ def fixture_reforms_dict(tests_path):
341
336
  NUM_REFORMS = 60 # when changing this also change num_reforms in conftest.py
342
337
 
343
338
 
344
- @pytest.mark.requires_pufcsv
345
339
  @pytest.mark.parametrize('rid', list(range(1, NUM_REFORMS + 1)))
346
340
  def test_reforms(rid, test_reforms_init, tests_path, baseline_2017_law,
347
- reforms_dict, puf_subsample):
341
+ reforms_dict, cps_subsample):
348
342
  """
349
343
  Write actual reform results to files.
350
344
  """
351
345
  # pylint: disable=too-many-arguments,too-many-positional-arguments
352
346
  assert test_reforms_init == NUM_REFORMS
353
347
  actual = reform_results(rid, reforms_dict[str(rid)],
354
- puf_subsample, baseline_2017_law)
348
+ cps_subsample, baseline_2017_law)
355
349
  afile_path = os.path.join(tests_path, f'reform_actual_{rid}.csv')
356
350
  with open(afile_path, 'w', encoding='utf-8') as afile:
357
351
  afile.write('rid,res1,res2,res3,res4\n')
358
352
  afile.write(f'{actual}\n')
359
353
 
360
354
 
361
- @pytest.mark.extend_tcja
362
- def test_ext_reform(tests_path):
355
+ @pytest.mark.parametrize('reform_filename, expected_diff', [
356
+ ('ext.json', 45.491),
357
+ ('OBBBA.json', 0.0),
358
+ ('NoOBBBA.json', 292.402),
359
+ ])
360
+ def test_reforms_cps(reform_filename, expected_diff, tests_path):
363
361
  """
364
- Test ext.json reform that extends TCJA beyond 2025.
362
+ Test reforms beyond 2025 using public CPS data.
365
363
  """
366
- # test syntax of ext.json reform file
367
- end = Policy()
368
- end.set_year(2026)
369
- ext = Policy()
370
- reform_file = os.path.join(tests_path, '..', 'reforms', 'ext.json')
364
+ pol = Policy()
365
+ reform_file = os.path.join(tests_path, '..', 'reforms', reform_filename)
371
366
  with open(reform_file, 'r', encoding='utf-8') as rfile:
372
367
  rtext = rfile.read()
373
- ext.implement_reform(Policy.read_json_reform(rtext))
374
- assert not ext.parameter_errors
375
- ext.set_year(2026)
376
- assert np.allclose([ext.II_em], [end.II_em])
377
- # test tax output generated by ext.json reform file using public CPS data
368
+ pol.implement_reform(Policy.read_json_reform(rtext))
369
+ assert not pol.parameter_errors
370
+
378
371
  recs = Records.cps_constructor()
379
- calc_end = Calculator(policy=end, records=recs, verbose=False)
380
- calc_end.advance_to_year(2026)
381
- calc_end.calc_all()
382
- iitax_end = calc_end.array('iitax')
383
- calc_ext = Calculator(policy=ext, records=recs, verbose=False)
384
- calc_ext.advance_to_year(2026)
385
- calc_ext.calc_all()
386
- iitax_ext = calc_ext.array('iitax')
387
- rdiff = iitax_ext - iitax_end
388
- weighted_sum_rdiff = (rdiff * calc_end.array('s006')).sum() * 1.0e-9
389
- assert np.allclose([weighted_sum_rdiff], [45.491], rtol=0.0, atol=0.01)
372
+
373
+ # create a Calculator object using current-law policy
374
+ calc_clp = Calculator(policy=Policy(), records=recs, verbose=False)
375
+ calc_clp.advance_to_year(2026)
376
+ calc_clp.calc_all()
377
+ iitax_clp = calc_clp.array('iitax')
378
+
379
+ # create a Calculator object using the reform
380
+ calc_ref = Calculator(policy=pol, records=recs, verbose=False)
381
+ calc_ref.advance_to_year(2026)
382
+ calc_ref.calc_all()
383
+ iitax_ref = calc_ref.array('iitax')
384
+
385
+ # compare aggregate individual income tax liability
386
+ rdiff = iitax_ref - iitax_clp
387
+ weighted_sum_rdiff = (rdiff * calc_clp.array('s006')).sum() * 1.0e-9
388
+ assert np.allclose([weighted_sum_rdiff], [expected_diff],
389
+ rtol=0.0, atol=0.01)
@@ -320,50 +320,10 @@ def test_init_errors(reformfile0, errorreformfile, errorassumpfile,
320
320
  # test TaxCalcIO.init method
321
321
  tcio.init(input_data=recdf, tax_year=year,
322
322
  baseline=baseline, reform=reform, assump=assump,
323
- aging_input_data=False,
324
323
  exact_calculations=True)
325
324
  assert tcio.errmsg
326
325
 
327
326
 
328
- def test_creation_with_aging(reformfile0):
329
- """
330
- Test TaxCalcIO instantiation with/without no policy reform and with aging.
331
- """
332
- taxyear = 2021
333
- tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
334
- tax_year=taxyear,
335
- baseline=None,
336
- reform=reformfile0.name,
337
- assump=None,
338
- silent=False)
339
- assert not tcio.errmsg
340
- tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
341
- tax_year=taxyear,
342
- baseline=None,
343
- reform=reformfile0.name,
344
- assump=None,
345
- aging_input_data=True,
346
- exact_calculations=False)
347
- assert not tcio.errmsg
348
- assert tcio.tax_year() == taxyear
349
- taxyear = 2016
350
- tcio = TaxCalcIO(input_data=pd.read_csv(StringIO(RAWINPUT)),
351
- tax_year=taxyear,
352
- baseline=None,
353
- reform=None,
354
- assump=None)
355
- assert not tcio.errmsg
356
- tcio.init(input_data=pd.read_csv(StringIO(RAWINPUT)),
357
- tax_year=taxyear,
358
- baseline=None,
359
- reform=None,
360
- assump=None,
361
- aging_input_data=True,
362
- exact_calculations=False)
363
- assert not tcio.errmsg
364
- assert tcio.tax_year() == taxyear
365
-
366
-
367
327
  def test_ctor_init_with_cps_files():
368
328
  """
369
329
  Test use of CPS input files.
@@ -372,32 +332,32 @@ def test_ctor_init_with_cps_files():
372
332
  txyr = 2020
373
333
  for rid in [0, 99]:
374
334
  tcio = TaxCalcIO('cps.csv', txyr, None, None, None, runid=rid)
375
- tcio.init(
376
- 'cps.csv', txyr, None, None, None,
377
- aging_input_data=True,
378
- exact_calculations=False,
379
- )
335
+ tcio.init('cps.csv', txyr, None, None, None, exact_calculations=False)
380
336
  assert not tcio.errmsg
381
337
  assert tcio.tax_year() == txyr
382
338
  # test advance_to_year method
383
339
  tcio.silent = False
384
- tcio.advance_to_year(txyr + 1, True)
340
+ tcio.advance_to_year(txyr + 1)
385
341
  assert tcio.tax_year() == txyr + 1
386
342
  # specify invalid tax_year for cps.csv input data
387
343
  txyr = 2013
388
344
  tcio = TaxCalcIO('cps.csv', txyr, None, None, None)
389
- tcio.init('cps.csv', txyr, None, None, None,
390
- aging_input_data=True,
391
- exact_calculations=False)
345
+ tcio.init('cps.csv', txyr, None, None, None, exact_calculations=False)
392
346
  assert tcio.errmsg
393
347
 
394
348
 
395
- @pytest.mark.parametrize("dumpvar_str, str_valid, num_vars", [
349
+ @pytest.mark.parametrize('dumpvar_str, str_valid, num_vars', [
396
350
  ("""
397
351
  MARS;iitax payrolltax|combined,
398
352
  c00100
399
353
  surtax
400
- """, True, 6), # these 6 parameters minus MARS plus RECID
354
+ """, True, 6), # these 6 variables minus MARS plus RECID
355
+
356
+ ('ALL', True, 209),
357
+ # 209 =
358
+ # all 212 vars in records_variables.json (see test_records.py)
359
+ # minus 5 TaxCalcIO.BASE_DUMPVARS omitting RECID (see taxcalcio.py)
360
+ # plus 2 TaxCalcIO.MTR_DUMPVARS (see taxcalcio.py)
401
361
 
402
362
  ("""
403
363
  MARS;iitax payrolltax|kombined,c00100
@@ -418,7 +378,6 @@ def test_dump_variables(dumpvar_str, str_valid, num_vars):
418
378
  assert not tcio.errmsg
419
379
  tcio.init(input_data=recdf, tax_year=year,
420
380
  baseline=None, reform=None, assump=None,
421
- aging_input_data=False,
422
381
  exact_calculations=False)
423
382
  assert not tcio.errmsg
424
383
  varlist = tcio.dump_variables(dumpvar_str)
@@ -445,7 +404,6 @@ def test_output_options_min(reformfile1, assumpfile1):
445
404
  baseline=None,
446
405
  reform=reformfile1.name,
447
406
  assump=assumpfile1.name,
448
- aging_input_data=False,
449
407
  exact_calculations=False)
450
408
  assert not tcio.errmsg
451
409
  dumppath = tcio.output_filepath().replace('.xxx', '.dumpdb')
@@ -480,7 +438,6 @@ def test_output_options_mtr(reformfile1, assumpfile1):
480
438
  baseline=None,
481
439
  reform=reformfile1.name,
482
440
  assump=assumpfile1.name,
483
- aging_input_data=False,
484
441
  exact_calculations=False)
485
442
  assert not tcio.errmsg
486
443
  dumppath = tcio.output_filepath().replace('.xxx', '.dumpdb')
@@ -520,7 +477,6 @@ def test_write_policy_param_files(reformfile1):
520
477
  baseline=compound_reform,
521
478
  reform=compound_reform,
522
479
  assump=None,
523
- aging_input_data=False,
524
480
  exact_calculations=False)
525
481
  assert not tcio.errmsg
526
482
  tcio.write_policy_params_files()
@@ -557,7 +513,6 @@ def test_no_tables_or_graphs(reformfile1):
557
513
  baseline=None,
558
514
  reform=reformfile1.name,
559
515
  assump=None,
560
- aging_input_data=False,
561
516
  exact_calculations=False)
562
517
  assert not tcio.errmsg
563
518
  # create several TaxCalcIO output files
@@ -593,7 +548,6 @@ def test_tables(reformfile1):
593
548
  baseline=None,
594
549
  reform=reformfile1.name,
595
550
  assump=None,
596
- aging_input_data=False,
597
551
  exact_calculations=False)
598
552
  assert not tcio.errmsg
599
553
  # create TaxCalcIO tables file
@@ -627,7 +581,6 @@ def test_graphs(reformfile1):
627
581
  baseline=None,
628
582
  reform=reformfile1.name,
629
583
  assump=None,
630
- aging_input_data=False,
631
584
  exact_calculations=False)
632
585
  assert not tcio.errmsg
633
586
  tcio.analyze(output_graphs=True)
@@ -671,7 +624,6 @@ def test_analyze_warnings_print(warnreformfile):
671
624
  baseline=None,
672
625
  reform=warnreformfile.name,
673
626
  assump=None,
674
- aging_input_data=False,
675
627
  exact_calculations=False)
676
628
  assert not tcio.errmsg
677
629
  tcio.analyze()
@@ -739,11 +691,10 @@ def test_error_message_parsed_correctly(regression_reform_file):
739
691
  baseline=regression_reform_file.name,
740
692
  reform=regression_reform_file.name,
741
693
  assump=None,
742
- aging_input_data=False,
743
694
  exact_calculations=False)
744
695
  assert isinstance(tcio.errmsg, str) and tcio.errmsg
745
696
  exp_errmsg = (
746
- "AMEDT_rt[year=2021] 1.8 > max 1 \n"
747
- "AMEDT_rt[year=2021] 1.8 > max 1 "
697
+ 'AMEDT_rt[year=2021] 1.8 > max 1 \n'
698
+ 'AMEDT_rt[year=2021] 1.8 > max 1 '
748
699
  )
749
700
  assert tcio.errmsg == exp_errmsg