def test_round_trip_tcja_reform(tests_path): """ Check that current-law policy has the same policy parameter values in a future year as does a compound reform that first implements the reform specified in the 2017_law.json file and then implements the reform specified in the TCJA.json file. This test checks that the future-year parameter values for current-law policy (which incorporates TCJA) are the same as future-year parameter values for the compound round-trip reform. Doing this check ensures that the 2017_law.json and TCJA.json reform files are specified in a consistent manner. """ # pylint: disable=too-many-locals fyear = 2020 # create clp metadata dictionary for current-law policy in fyear pol = Policy() pol.set_year(fyear) clp_mdata = pol.metadata() # create rtr metadata dictionary for round-trip reform in fyear pol = Policy() reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) # eventually activate: assert not clp.parameter_warnings ctc_c_warning = 'CTC_c was redefined in release 1.0.0\n' assert pol.parameter_warnings == ctc_c_warning assert not pol.parameter_errors reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) # eventually activate: assert not clp.parameter_warnings assert pol.parameter_warnings == ctc_c_warning assert not pol.parameter_errors pol.set_year(fyear) rtr_mdata = pol.metadata() # compare fyear policy parameter values assert clp_mdata.keys() == rtr_mdata.keys() fail_dump = False if fail_dump: rtr_fails = open('fails_rtr', 'w') clp_fails = open('fails_clp', 'w') fail_params = list() msg = '\nRound-trip-reform and current-law-policy param values differ for:' for pname in clp_mdata.keys(): rtr_val = rtr_mdata[pname]['value'] clp_val = clp_mdata[pname]['value'] if not np.allclose(rtr_val, clp_val): fail_params.append(pname) msg += '\n {} in {} : rtr={} clp={}'.format( pname, fyear, rtr_val, clp_val ) if fail_dump: rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val)) clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val)) if fail_dump: rtr_fails.close() clp_fails.close() if fail_params: raise ValueError(msg)
def test_round_trip_tcja_reform(tests_path): """ Check that current-law policy has the same policy parameter values in a future year as does a compound reform that first implements the reform specified in the 2017_law.json file and then implements the reform specified in the TCJA.json file. This test checks that the future-year parameter values for current-law policy (which incorporates TCJA) are the same as future-year parameter values for the compound round-trip reform. Doing this check ensures that the 2017_law.json and TCJA.json reform files are specified in a consistent manner. """ # pylint: disable=too-many-locals fyear = 2020 # create clp metadata dictionary for current-law policy in fyear pol = Policy() pol.set_year(fyear) clp_mdata = pol.metadata() # create rtr metadata dictionary for round-trip reform in fyear pol = Policy() reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) # eventually activate: assert not clp.parameter_warnings ctc_c_warning = 'CTC_c was redefined in release 1.0.0\n' assert pol.parameter_warnings == ctc_c_warning assert not pol.parameter_errors reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) # eventually activate: assert not clp.parameter_warnings assert pol.parameter_warnings == ctc_c_warning assert not pol.parameter_errors pol.set_year(fyear) rtr_mdata = pol.metadata() # compare fyear policy parameter values assert clp_mdata.keys() == rtr_mdata.keys() fail_dump = False if fail_dump: rtr_fails = open('fails_rtr', 'w') clp_fails = open('fails_clp', 'w') fail_params = list() msg = '\nRound-trip-reform and current-law-policy param values differ for:' for pname in clp_mdata.keys(): rtr_val = rtr_mdata[pname]['value'] clp_val = clp_mdata[pname]['value'] if not np.allclose(rtr_val, clp_val): fail_params.append(pname) msg += '\n {} in {} : rtr={} clp={}'.format( pname, fyear, rtr_val, clp_val) if fail_dump: rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val)) clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val)) if fail_dump: rtr_fails.close() clp_fails.close() if fail_params: raise ValueError(msg)
def test_2017_law_reform(tests_path): """ Check that policy parameter values in a future year under current-law policy and under the reform specified in the 2017_law.json file are sensible. """ # create pre metadata dictionary for 2017_law.json reform in fyear pol = Policy() reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) # eventually activate: assert not clp.parameter_warnings ctc_c_warning = 'CTC_c was redefined in release 1.0.0\n' assert pol.parameter_warnings == ctc_c_warning assert not pol.parameter_errors pol.set_year(2018) pre_mdata = pol.metadata() # check some policy parameter values against expected values under 2017 law pre_expect = { # relation '<' implies asserting that actual < expect # relation '>' implies asserting that actual > expect # ... parameters not affected by TCJA and that are not indexed 'AMEDT_ec': {'relation': '=', 'value': 200000}, 'SS_thd85': {'relation': '=', 'value': 34000}, # ... parameters not affected by TCJA and that are indexed 'STD_Dep': {'relation': '>', 'value': 1050}, 'CG_brk2': {'relation': '>', 'value': 425800}, 'AMT_CG_brk1': {'relation': '>', 'value': 38600}, 'AMT_brk1': {'relation': '>', 'value': 191100}, 'EITC_c': {'relation': '>', 'value': 519}, 'EITC_ps': {'relation': '>', 'value': 8490}, 'EITC_ps_MarriedJ': {'relation': '>', 'value': 5680}, 'EITC_InvestIncome_c': {'relation': '>', 'value': 3500}, # ... parameters affected by TCJA and that are not indexed 'ID_Charity_crt_all': {'relation': '=', 'value': 0.5}, 'II_rt3': {'relation': '=', 'value': 0.25}, # ... parameters affected by TCJA and that are indexed 'II_brk3': {'relation': '>', 'value': 91900}, 'STD': {'relation': '<', 'value': 7000}, 'II_em': {'relation': '>', 'value': 4050}, 'AMT_em_pe': {'relation': '<', 'value': 260000} } assert isinstance(pre_expect, dict) assert set(pre_expect.keys()).issubset(set(pre_mdata.keys())) for name in pre_expect: aval = pre_mdata[name]['value'] if isinstance(aval, list): act = aval[0] # comparing only first item in a vector parameter else: act = aval exp = pre_expect[name]['value'] if pre_expect[name]['relation'] == '<': assert act < exp, '{} a={} !< e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '>': assert act > exp, '{} a={} !> e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '=': assert act == exp, '{} a={} != e={}'.format(name, act, exp)
def test_json_reform_url(): """ Test reading a JSON reform from a URL. Results from the URL are expected to match the results from the string. """ reform_str = """ { // raise FICA payroll tax rate in 2018 and 2020 "FICA_ss_trt": { "2018": 0.130, "2020": 0.140 }, // raise Medicare payroll tax rate in 2019 and 2021 "FICA_mc_trt": { "2019": 0.030, "2021": 0.032 } } """ reform_url = ('https://raw.githubusercontent.com/PSLmodels/' 'Tax-Calculator/master/taxcalc/reforms/ptaxes0.json') params_str = Policy.read_json_reform(reform_str) params_url = Policy.read_json_reform(reform_url) assert params_str == params_url
def test_puf_var_stats(tests_path, puf_fullsample): """ Main logic of test. """ # create a baseline Policy object containing 2017_law.json parameters pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Policy.read_json_reform(pre_tcja_jrf) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja) # create a Calculator object using baseline_policy and full puf.csv sample rec = Records(data=puf_fullsample) calc = Calculator(policy=baseline_policy, records=rec, verbose=False) # create base tables table_mean = create_base_table(tests_path) table_corr = copy.deepcopy(table_mean) del table_corr['description'] # add statistics to tables year_headers = ['description'] for year in range(Policy.JSON_START_YEAR, Policy.LAST_BUDGET_YEAR + 1): assert year == calc.current_year year_headers.append(str(year)) calc.calc_all() calculate_mean_stats(calc, table_mean, year) if year == 2016: calculate_corr_stats(calc, table_corr) if year < Policy.LAST_BUDGET_YEAR: calc.increment_year() # write tables to new CSV files mean_path = os.path.join(tests_path, MEAN_FILENAME + '-new') table_mean.sort_index(inplace=True) table_mean.to_csv(mean_path, header=year_headers, float_format='%8.0f') corr_path = os.path.join(tests_path, CORR_FILENAME + '-new') table_corr.sort_index(inplace=True) table_corr.to_csv(corr_path, float_format='%8.2f', columns=table_corr.index) # compare new and old CSV files for differences mean_msg = differences(mean_path, mean_path[:-4], 'MEAN') corr_msg = differences(corr_path, corr_path[:-4], 'CORR') if mean_msg or corr_msg: raise ValueError(mean_msg + corr_msg)
def test_read_json_reform_file_and_implement_reform(set_year): """ Test reading and translation of reform JSON into a reform dictionary and then using that reform dictionary to implement reform. """ pol = Policy() if set_year: pol.set_year(2015) pol.implement_reform(Policy.read_json_reform(REFORM_JSON)) syr = pol.start_year # pylint: disable=protected-access amt_brk1 = pol._AMT_brk1 assert amt_brk1[2015 - syr] == 200000 assert amt_brk1[2016 - syr] > 200000 assert amt_brk1[2017 - syr] == 300000 assert amt_brk1[2018 - syr] > 300000 ii_em = pol._II_em assert ii_em[2016 - syr] == 6000 assert ii_em[2017 - syr] == 6000 assert ii_em[2018 - syr] == 7500 assert ii_em[2019 - syr] > 7500 assert ii_em[2020 - syr] == 9000 assert ii_em[2021 - syr] > 9000 amt_em = pol._AMT_em assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0] assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0] assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0] assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0] add4aged = pol._ID_Medical_frt_add4aged assert add4aged[2015 - syr] == -0.025 assert add4aged[2016 - syr] == -0.025 assert add4aged[2017 - syr] == 0.0 assert add4aged[2022 - syr] == 0.0
def fixture_baseline_2017_law(): """ Read ../taxcalc/2017_law.json and return its policy dictionary. """ pre_tcja_jrf = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') return Policy.read_json_reform(pre_tcja_jrf)
def test_2017_law_reform(): """ Check that policy parameter values in a future year under current-law policy and under the reform specified in the 2017_law.json file are sensible. """ # create pre metadata dictionary for 2017_law.json reform in fyear pol = Policy() reform_file = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings pol.set_year(2018) pre_mdata = dict(pol.items()) # check some policy parameter values against expected values under 2017 law pre_expect = { # relation '<' implies asserting that actual < expect # relation '>' implies asserting that actual > expect # ... parameters not affected by TCJA and that are not indexed 'AMEDT_ec': { 'relation': '=', 'value': 200000 }, 'SS_thd85': { 'relation': '=', 'value': 34000 }, # ... parameters not affected by TCJA and that are indexed 'STD_Dep': { 'relation': '>', 'value': 1050 }, 'CG_brk2': { 'relation': '>', 'value': 425800 }, 'AMT_CG_brk1': { 'relation': '>', 'value': 38600 }, 'AMT_brk1': { 'relation': '>', 'value': 191100 }, 'EITC_c': { 'relation': '>', 'value': 519 }, 'EITC_ps': { 'relation': '>', 'value': 8490 }, 'EITC_ps_MarriedJ': { 'relation': '>', 'value': 5680 }, 'EITC_InvestIncome_c': { 'relation': '>', 'value': 3500 }, # ... parameters affected by TCJA and that are not indexed 'ID_Charity_crt_all': { 'relation': '=', 'value': 0.5 }, 'II_rt3': { 'relation': '=', 'value': 0.25 }, # ... parameters affected by TCJA and that are indexed 'II_brk3': { 'relation': '>', 'value': 91900 }, 'STD': { 'relation': '<', 'value': 7000 }, 'II_em': { 'relation': '>', 'value': 4050 }, 'AMT_em_pe': { 'relation': '<', 'value': 260000 } } assert isinstance(pre_expect, dict) assert set(pre_expect.keys()).issubset(set(pre_mdata.keys())) for name in pre_expect: aval = pre_mdata[name] if aval.ndim == 2: act = aval[0][0] # comparing only first item in a vector parameter else: act = aval[0] exp = pre_expect[name]['value'] if pre_expect[name]['relation'] == '<': assert act < exp, '{} a={} !< e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '>': assert act > exp, '{} a={} !> e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '=': assert act == exp, '{} a={} != e={}'.format(name, act, exp)
def test_reform_json_and_output(): """ Check that each JSON reform file can be converted into a reform dictionary that can then be passed to the Policy class implement_reform method that generates no parameter_errors. Then use each reform to generate static tax results for small set of filing units in a single tax_year and compare those results with expected results from a CSV-formatted file. """ # pylint: disable=too-many-statements,too-many-locals # embedded function used only in test_reform_json_and_output def write_res_file(calc, resfilename): """ Write calc output to CSV-formatted file with resfilename. """ varlist = [ 'RECID', 'c00100', 'standard', 'c04800', 'iitax', 'payrolltax' ] # varnames AGI STD TaxInc ITAX PTAX stats = calc.dataframe(varlist) stats['RECID'] = stats['RECID'].astype(int) with open(resfilename, 'w') as resfile: stats.to_csv(resfile, index=False, float_format='%.2f') # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res.csv and base.out.csv file contents are same; return False if base.res.csv and base.out.csv file contents differ. """ resdf = pd.read_csv(base + '.res.csv') outdf = pd.read_csv(base + '.out.csv') diffs = False for col in resdf: if col in outdf: if not np.allclose(resdf[col], outdf[col]): diffs = True else: diffs = True return not diffs # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(CUR_PATH, '..', 'taxcalc', 'cases.csv') cases = Records( data=cases_path, start_year=tax_year, # set raw input data year gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None) # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc = Calculator(policy=Policy(), records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc # read 2017_law.json reform file and specify its parameters dictionary pre_tcja_jrf = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') pre_tcja = Policy.read_json_reform(pre_tcja_jrf) # check reform file contents and reform results for each reform reforms_path = os.path.join(CUR_PATH, '..', 'taxcalc', '*.json') json_reform_files = glob.glob(reforms_path) for jrf in json_reform_files: # determine reform's baseline by reading contents of jrf with open(jrf, 'r') as rfile: jrf_text = rfile.read() pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text # implement the reform relative to its baseline reform = Policy.read_json_reform(jrf_text) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja) assert not pol.parameter_errors pol.implement_reform(reform) assert not pol.parameter_errors calc = Calculator(policy=pol, records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = jrf.replace('.json', '.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc if failures: msg = 'Following reforms have res-vs-out differences:\n' for ref in failures: msg += '{}\n'.format(os.path.basename(ref)) raise ValueError(msg)
def fixture_baseline_2017_law(tests_path): """ Read ../reforms/2017_law.json and return its policy dictionary. """ pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') return Policy.read_json_reform(pre_tcja_jrf)
def test_round_trip_reforms(fyear, tests_path): """ Check that current-law policy has the same policy parameter values in a future year as does a compound reform that first implements the 2017 tax law as specified in the 2017_law.json file and then implements reforms that represents new tax legislation since 2017. This test checks that the future-year parameter values for current-law policy (which incorporates recent legislation such as the TCJA, CARES Act, and ARPA) are the same as future-year parameter values for the compound round-trip reform. Doing this check ensures that the 2017_law.json and subsequent reform files that represent recent legislation are specified in a consistent manner. """ # pylint: disable=too-many-locals # create clp metadata dictionary for current-law policy in fyear clp_pol = Policy() clp_pol.set_year(fyear) clp_mdata = dict(clp_pol.items()) # create rtr metadata dictionary for round-trip reform in fyear rtr_pol = Policy() # Revert to 2017 law reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() rtr_pol.implement_reform(Policy.read_json_reform(rtext)) assert not rtr_pol.parameter_warnings assert not rtr_pol.errors # Layer on TCJA reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() rtr_pol.implement_reform(Policy.read_json_reform(rtext)) assert not rtr_pol.parameter_warnings assert not rtr_pol.errors # Layer on the CARES Act rtr_pol.implement_reform({ 'ID_Charity_crt_all': { 2020: 1.0, 2021: 0.6 }, 'STD_allow_charity_ded_nonitemizers': { 2020: True, 2021: False }, 'STD_charity_ded_nonitemizers_max': { 2020: 300.0, 2021: 0.0 } }) assert not rtr_pol.parameter_warnings assert not rtr_pol.errors # Layer on ARPA rtr_pol.implement_reform({ 'RRC_c': { 2021: 1400, 2022: 0 }, 'RRC_ps': { 2021: [75000, 150000, 75000, 112500, 150000], 2022: [0, 0, 0, 0, 0] }, 'RRC_pe': { 2021: [80000, 160000, 80000, 120000, 160000], 2022: [0, 0, 0, 0, 0] }, 'UI_em': { 2020: 10200, 2021: 0 }, 'UI_thd': { 2020: [150000, 150000, 150000, 150000, 150000], 2021: [0, 0, 0, 0, 0] }, 'CTC_refundable': { 2021: True, 2022: False }, 'CTC_include17': { 2021: True, 2022: False }, 'CTC_new_c': { 2021: 1000, 2022: 0 }, 'CTC_new_c_under6_bonus': { 2021: 600, 2022: 0 }, 'CTC_new_for_all': { 2021: True, 2022: False }, 'CTC_new_ps': { 2021: [75000, 150000, 75000, 112500, 150000], 2022: [0, 0, 0, 0, 0] }, 'CTC_new_prt': { 2021: 0.05, 2022: 0 }, 'EITC_c': { 2021: [1502.46, 3606.44, 5960.95, 6706.58], 2022: [546.21, 3640.7, 6017.58, 6770.29] }, 'EITC_rt': { 2021: [0.153, 0.34, 0.4, 0.45], 2022: [0.0765, 0.34, 0.4, 0.45] }, 'EITC_ps': { 2021: [11610, 19464.12, 19464.12, 19464.12], 2022: [8931.38, 19649.03, 19649.03, 19649.03] }, 'EITC_MinEligAge': { 2021: 19, 2022: 25 }, 'EITC_MaxEligAge': { 2021: 125, 2022: 64 }, 'EITC_InvestIncome_c': { 2021: 10000 }, 'EITC_sep_filers_elig': { 2021: True }, 'CDCC_c': { 2021: 8000, 2022: 3000 }, 'CDCC_ps': { 2021: 125000, 2022: 15000 }, 'CDCC_ps2': { 2021: 400000, 2022: 9e+99 }, 'CDCC_crt': { 2021: 50.0, 2022: 35.0 }, 'CDCC_refundable': { 2021: True, 2022: False }, 'ALD_BusinessLosses_c': { 2026: [283535.22, 567070.42, 283535.22, 283535.22, 567070.42], 2027: [9e+99, 9e+99, 9e+99, 9e+99, 9e+99] } }) assert not rtr_pol.parameter_warnings assert not rtr_pol.errors rtr_pol.set_year(fyear) rtr_mdata = dict(rtr_pol.items()) # compare fyear policy parameter values assert clp_mdata.keys() == rtr_mdata.keys() fail_dump = False if fail_dump: rtr_fails = open('fails_rtr', 'w') clp_fails = open('fails_clp', 'w') fail_params = list() msg = '\nRound-trip-reform and current-law-policy param values differ for:' for pname in clp_mdata.keys(): rtr_val = rtr_mdata[pname] clp_val = clp_mdata[pname] if not np.allclose(rtr_val, clp_val): fail_params.append(pname) msg += '\n {} in {} : rtr={} clp={}'.format( pname, fyear, rtr_val, clp_val) if fail_dump: rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val)) clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val)) if fail_dump: rtr_fails.close() clp_fails.close() if fail_params: raise ValueError(msg)
def test_read_json_reform_and_implement_reform(set_year): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method. NOTE: implement_reform called when policy.current_year == policy.start_year """ reform_json = """ // Example of JSON reform text suitable for the // Policy.read_json_reform() method. // This JSON text can contain any number of trailing //-style comments, // which will be removed before the contents are converted from JSON to // a dictionary. // The primary keys are policy parameters and secondary keys are years. // Both the primary & secondary key values must be enclosed in quotes ("). // Boolean variables are specified as true or false with no quotes and all // lowercase characters. { "AMT_brk1": // top of first AMT tax bracket {"2015": 200000, "2017": 300000 }, "EITC_c": // max EITC amount by number of qualifying kids (0,1,2,3+) {"2016": [ 900, 5000, 8000, 9000], "2019": [1200, 7000, 10000, 12000] }, "II_em": // personal exemption amount (see indexing changes below) {"2016": 6000, "2018": 7500, "2020": 9000 }, "II_em-indexed": // personal exemption amount indexing status {"2016": false, // values in future years are same as this year value "2018": true // vals in future years indexed with this year as base }, "SS_Earnings_c": // Social Security (OASDI) maximum taxable earnings {"2016": 300000, "2018": 500000, "2020": 700000 }, "AMT_em-indexed": // AMT exemption amount indexing status {"2017": false, // values in future years are same as this year value "2020": true // vals in future years indexed with this year as base } } """ policy = Policy() if set_year: policy.set_year(2015) reform_dict = Policy.read_json_reform(reform_json) policy.implement_reform(reform_dict) syr = policy.start_year amt_brk1 = policy._AMT_brk1 assert amt_brk1[2015 - syr] == 200000 assert amt_brk1[2016 - syr] > 200000 assert amt_brk1[2017 - syr] == 300000 assert amt_brk1[2018 - syr] > 300000 ii_em = policy._II_em assert ii_em[2016 - syr] == 6000 assert ii_em[2017 - syr] == 6000 assert ii_em[2018 - syr] == 7500 assert ii_em[2019 - syr] > 7500 assert ii_em[2020 - syr] == 9000 assert ii_em[2021 - syr] > 9000 amt_em = policy._AMT_em assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0] assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0] assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0] assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0] add4aged = policy._ID_Medical_frt_add4aged assert add4aged[2015 - syr] == -0.025 assert add4aged[2016 - syr] == -0.025 assert add4aged[2017 - syr] == 0.0 assert add4aged[2022 - syr] == 0.0
def test_round_trip_tcja_reform(tests_path): """ Check that current-law policy has the same policy parameter values in a future year as does a compound reform that first implements the reform specified in the 2017_law.json file and then implements the reform specified in the TCJA.json file. This test checks that the future-year parameter values for current-law policy (which incorporates TCJA) are the same as future-year parameter values for the compound round-trip reform. Doing this check ensures that the 2017_law.json and TCJA.json reform files are specified in a consistent manner. """ # pylint: disable=too-many-locals fyear = 2020 # create clp metadata dictionary for current-law policy in fyear pol = Policy() pol.set_year(fyear) clp_mdata = dict(pol.items()) # create rtr metadata dictionary for round-trip reform in fyear pol = Policy() # Revert to 2017 law reform_file = os.path.join(tests_path, '..', 'reforms', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings assert not pol.errors # Layer on TCJA reform_file = os.path.join(tests_path, '..', 'reforms', 'TCJA.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings assert not pol.errors # Layer on the CARES Act pol.implement_reform({ 'ID_Charity_crt_all': { 2020: 1.0, 2021: 0.6 }, 'STD_allow_charity_ded_nonitemizers': { 2020: True, 2021: False }, 'STD_charity_ded_nonitemizers_max': { 2020: 300.0, 2021: 0.0 } }) assert not pol.parameter_warnings assert not pol.errors pol.set_year(fyear) rtr_mdata = dict(pol.items()) # compare fyear policy parameter values assert clp_mdata.keys() == rtr_mdata.keys() fail_dump = False if fail_dump: rtr_fails = open('fails_rtr', 'w') clp_fails = open('fails_clp', 'w') fail_params = list() msg = '\nRound-trip-reform and current-law-policy param values differ for:' for pname in clp_mdata.keys(): rtr_val = rtr_mdata[pname] clp_val = clp_mdata[pname] if not np.allclose(rtr_val, clp_val): fail_params.append(pname) msg += '\n {} in {} : rtr={} clp={}'.format( pname, fyear, rtr_val, clp_val) if fail_dump: rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val)) clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val)) if fail_dump: rtr_fails.close() clp_fails.close() if fail_params: raise ValueError(msg)
def test_reform_json_and_output(tests_path): """ Check that each JSON reform file can be converted into a reform dictionary that can then be passed to the Policy class implement_reform method that generates no parameter_errors. Then use each reform to generate static tax results for small set of filing units in a single tax_year and compare those results with expected results from a CSV-formatted file. """ # pylint: disable=too-many-statements,too-many-locals # embedded function used only in test_reform_json_and_output def write_res_file(calc, resfilename): """ Write calc output to CSV-formatted file with resfilename. """ varlist = [ 'RECID', 'c00100', 'standard', 'c04800', 'iitax', 'payrolltax' ] # varnames AGI STD TaxInc ITAX PTAX stats = calc.dataframe(varlist) stats['RECID'] = stats['RECID'].astype(int) with open(resfilename, 'w') as resfile: stats.to_csv(resfile, index=False, float_format='%.2f') # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res.csv and base.out.csv file contents are same; return False if base.res.csv and base.out.csv file contents differ. """ resdf = pd.read_csv(base + '.res.csv') outdf = pd.read_csv(base + '.out.csv') diffs = False for col in resdf: if col in outdf: if not np.allclose(resdf[col], outdf[col]): diffs = True else: diffs = True return not diffs # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv') cases = Records(data=cases_path, start_year=tax_year, # set raw input data year gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None) # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc = Calculator(policy=Policy(), records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc # read 2017_law.json reform file and specify its parameters dictionary pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Policy.read_json_reform(pre_tcja_jrf) # check reform file contents and reform results for each reform reforms_path = os.path.join(tests_path, '..', 'reforms', '*.json') json_reform_files = glob.glob(reforms_path) for jrf in json_reform_files: # determine reform's baseline by reading contents of jrf with open(jrf, 'r') as rfile: jrf_text = rfile.read() pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text # implement the reform relative to its baseline reform = Policy.read_json_reform(jrf_text) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja) assert not pol.parameter_errors pol.implement_reform(reform) assert not pol.parameter_errors calc = Calculator(policy=pol, records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = jrf.replace('.json', '.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc if failures: msg = 'Following reforms have res-vs-out differences:\n' for ref in failures: msg += '{}\n'.format(os.path.basename(ref)) raise ValueError(msg)