def test_translate_json_reform_suffixes_eic(): """ Test read_json_param_objects method using EIC-indexed parameter suffixes. """ json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_EITC_c_0kids": {"2018": [510], "2019": [510]}, "_EITC_c_1kid": {"2019": [3400], "2018": [3400]}, "_EITC_c_2kids": {"2018": [5616], "2019": [5616]}, "_EITC_c_3+kids": {"2019": [6318], "2018": [6318]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_EITC_c": {"2019": [[510, 3400, 5616, 6318]], "2018": [[510, 3400, 5616, 6318]]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_EITC_c' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_EITC_c'], rdict2[year]['_EITC_c'], atol=0.01, rtol=0.0)
def test_translate_json_reform_suffixes_mars_non_indexed(): """ Test read_json_param_objects method using MARS-indexed parameter suffixes. """ json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_AMEDT_ec_joint": {"2018": [400000], "2016": [300000]}, "_AMEDT_ec_separate": {"2017": [150000], "2019": [200000]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_AMEDT_ec": {"2016": [[200000, 300000, 125000, 200000, 200000]], "2017": [[200000, 300000, 150000, 200000, 200000]], "2018": [[200000, 400000, 150000, 200000, 200000]], "2019": [[200000, 400000, 200000, 200000, 200000]]}, "_II_em": {"2015": [15000], "2020": [20000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_AMEDT_ec' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_AMEDT_ec'], rdict2[year]['_AMEDT_ec'], atol=0.01, rtol=0.0)
def test_translate_json_reform_suffixes_mars_non_indexed(): # test read_json_param_objects() # using MARS-indexed parameter suffixes json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_AMEDT_ec_joint": {"2018": [400000], "2016": [300000]}, "_AMEDT_ec_separate": {"2017": [150000], "2019": [200000]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_AMEDT_ec": {"2016": [[200000, 300000, 125000, 200000, 200000]], "2017": [[200000, 300000, 150000, 200000, 200000]], "2018": [[200000, 400000, 150000, 200000, 200000]], "2019": [[200000, 400000, 200000, 200000, 200000]]}, "_II_em": {"2015": [15000], "2020": [20000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_AMEDT_ec' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_AMEDT_ec'], rdict2[year]['_AMEDT_ec'], atol=0.01, rtol=0.0)
def test_translate_json_reform_suffixes_eic(): # test read_json_param_objects(...) # using EIC-indexed parameter suffixes json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_EITC_c_0kids": {"2018": [510], "2019": [510]}, "_EITC_c_1kid": {"2019": [3400], "2018": [3400]}, "_EITC_c_2kids": {"2018": [5616], "2019": [5616]}, "_EITC_c_3+kids": {"2019": [6318], "2018": [6318]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_EITC_c": {"2019": [[510, 3400, 5616, 6318]], "2018": [[510, 3400, 5616, 6318]]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_EITC_c' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_EITC_c'], rdict2[year]['_EITC_c'], atol=0.01, rtol=0.0)
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 = """ { "policy": { // 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 = Calculator.read_json_param_objects(reform_str, None) params_url = Calculator.read_json_param_objects(reform_url, None) assert params_str == params_url
def test_json_doesnt_exist(): """ Test JSON file which doesn't exist """ with pytest.raises(ValueError): Calculator.read_json_param_objects(None, './reforms/doesnt_exist.json') with pytest.raises(ValueError): Calculator.read_json_param_objects('./reforms/doesnt_exist.json', None)
def test_read_bad_json_assump_file(bad1assumpfile, bad2assumpfile, bad3assumpfile): with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad1assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad2assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad3assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_read_bad_json_assump_file(bad1assumpfile, bad2assumpfile, bad3assumpfile): """ Test invalid JSON assumption files. """ with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad1assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad2assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, bad3assumpfile.name) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_reform_documentation(): reform_json = """ { "policy": { "_II_em_cpi": {"2016": false, "2018": true}, "_II_em": {"2016": [5000], "2018": [6000], "2020": [7000]}, "_EITC_indiv": {"2017": [true]}, "_STD_Aged_cpi": {"2016": false}, "_STD_Aged": {"2016": [[1600, 1300, 1300, 1600, 1600]], "2020": [[2000, 2000, 2000, 2000, 2000]]}, "_ID_BenefitCap_Switch_medical": {"2020": [false]}, "_ID_BenefitCap_Switch_casualty": {"2020": [false]}, "_ID_BenefitCap_Switch_misc": {"2020": [false]}, "_ID_BenefitCap_Switch_interest": {"2020": [false]}, "_ID_BenefitCap_Switch_charity": {"2020": [false]} } } """ params = Calculator.read_json_param_objects(reform_json, None) assert isinstance(params, dict) doc = Calculator.reform_documentation(params) assert isinstance(doc, str) dump = False # set to True to print documentation and force test failure if dump: print(doc) assert 1 == 2
def test_read_json_param_with_suffixes_and_errors(): # test interaction of policy parameter suffixes and reform errors # (fails without 0.10.2 bug fix as reported by Hank Doupe in PB PR#641) reform = { u'policy': { u'_II_brk4_separate': {u'2017': [5000.0]}, u'_STD_separate': {u'2017': [8000.0]}, u'_STD_single': {u'2018': [1000.0]}, u'_II_brk2_headhousehold': {u'2017': [1000.0]}, u'_II_brk4_single': {u'2017': [500.0]}, u'_STD_joint': {u'2017': [10000.0], u'2020': [150.0]}, u'_II_brk2_separate': {u'2017': [1000.0]}, u'_II_brk2_single': {u'2017': [1000.0]}, u'_II_brk2_joint': {u'2017': [1000.0]}, u'_FICA_ss_trt': {u'2017': [-1.0], u'2019': [0.1]}, u'_II_brk4_headhousehold': {u'2017': [500.0]}, u'_STD_headhousehold': {u'2017': [10000.0], u'2020': [150.0]}, u'_II_brk4_joint': {u'2017': [500.0]}, u'_ID_BenefitSurtax_Switch_medical': {u'2017': [True]} } } json_reform = json.dumps(reform) params = Calculator.read_json_param_objects(json_reform, None) assert isinstance(params, dict) pol = Policy() pol.implement_reform(params['policy']) assert len(pol.reform_errors) > 0 assert len(pol.reform_warnings) > 0
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') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) return pre_tcja['policy']
def test_noreform_documentation(): """ Test automatic documentation creation. """ reform_json = """ { "policy": {} } """ assump_json = """ { "consumption": {}, "growdiff_baseline": {}, "growdiff_response": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) actual_doc = Calculator.reform_documentation(params) expected_doc = ( 'REFORM DOCUMENTATION\n' 'Baseline Growth-Difference Assumption Values by Year:\n' 'none: using default baseline growth assumptions\n' 'Policy Reform Parameter Values by Year:\n' 'none: using current-law policy parameters\n' ) assert actual_doc == expected_doc
def test_read_json_param_with_suffixes_and_errors(): """ Test interaction of policy parameter suffixes and reform errors (fails without 0.10.2 bug fix as reported by Hank Doupe in PB PR#641) """ reform = { u'policy': { u'_II_brk4_separate': {u'2017': [5000.0]}, u'_STD_separate': {u'2017': [8000.0]}, u'_STD_single': {u'2018': [1000.0]}, u'_II_brk2_headhousehold': {u'2017': [1000.0]}, u'_II_brk4_single': {u'2017': [500.0]}, u'_STD_joint': {u'2017': [10000.0], u'2020': [150.0]}, u'_II_brk2_separate': {u'2017': [1000.0]}, u'_II_brk2_single': {u'2017': [1000.0]}, u'_II_brk2_joint': {u'2017': [1000.0]}, u'_FICA_ss_trt': {u'2017': [-1.0], u'2019': [0.1]}, u'_II_brk4_headhousehold': {u'2017': [500.0]}, u'_STD_headhousehold': {u'2017': [10000.0], u'2020': [150.0]}, u'_ID_Medical_frt': {u'2019': [0.06]}, u'_II_brk4_joint': {u'2017': [500.0]}, u'_ID_BenefitSurtax_Switch_medical': {u'2017': [True]} } } json_reform = json.dumps(reform) params = Calculator.read_json_param_objects(json_reform, None) assert isinstance(params, dict) pol = Policy() pol.ignore_reform_errors() pol.implement_reform(params['policy'], print_warnings=False, raise_errors=False) assert pol.parameter_errors assert pol.parameter_warnings
def test_noreform_documentation(): """ Test automatic documentation creation. """ reform_json = """ { } """ assump_json = """ { "consumption": {}, "growdiff_baseline": {}, "growdiff_response": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) actual_doc = Calculator.reform_documentation(params) expected_doc = ('REFORM DOCUMENTATION\n' 'Baseline Growth-Difference Assumption Values by Year:\n' 'none: using default growth assumptions\n' 'Response Growth-Difference Assumption Values by Year:\n' 'none: using default growth assumptions\n' 'Policy Reform Parameter Values by Year:\n' 'none: using current-law policy parameters\n') assert actual_doc == expected_doc
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') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) return pre_tcja['policy']
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() reform = Calculator.read_json_param_objects(rtext, None) pol.implement_reform(reform['policy']) # 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_read_bad_json_reform_file(bad1reformfile, bad2reformfile, bad3reformfile): with pytest.raises(ValueError): Calculator.read_json_param_objects(bad1reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(bad2reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(bad3reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(list(), None)
def test_validate_param_names_types_errors(): """ Check detection of invalid policy parameter names and types in reforms. """ pol0 = Policy() ref0 = {2020: {'_STD_cpi': 2}} with pytest.raises(ValueError): pol0.implement_reform(ref0) pol1 = Policy() ref1 = {2020: {'_badname_cpi': True}} with pytest.raises(ValueError): pol1.implement_reform(ref1) pol2 = Policy() ref2 = {2020: {'_II_em_cpi': 5}} with pytest.raises(ValueError): pol2.implement_reform(ref2) pol3 = Policy() ref3 = {2020: {'_badname': [0.4]}} with pytest.raises(ValueError): pol3.implement_reform(ref3) pol4 = Policy() ref4 = {2020: {'_EITC_MinEligAge': [21.4]}} with pytest.raises(ValueError): pol4.implement_reform(ref4) pol5 = Policy() ref5 = {2025: {'_ID_BenefitSurtax_Switch': [[False, True, 0, 1, 0, 1, 0]]}} with pytest.raises(ValueError): pol5.implement_reform(ref5) pol6 = Policy() ref6 = {2021: {'_II_em': ['not-a-number']}} with pytest.raises(ValueError): pol6.implement_reform(ref6) pol7 = Policy() ref7 = {2019: {'_FICA_ss_trt_cpi': True}} with pytest.raises(ValueError): pol7.implement_reform(ref7) # test 8 was contributed by Hank Doupe in bug report #1956 pol8 = Policy() ref8 = {2019: {'_AMEDT_rt': [True]}} with pytest.raises(ValueError): pol8.implement_reform(ref8) # test 9 extends test 8 to integer parameters pol9 = Policy() ref9 = {2019: {'_AMT_KT_c_Age': [True]}} with pytest.raises(ValueError): pol9.implement_reform(ref9) # test 10 was contributed by Hank Doupe in bug report #1980 json_reform = """ {"policy": {"_ID_BenefitSurtax_Switch_medical": {"2018": [true]}}} """ pdict = Calculator.read_json_param_objects(json_reform, None) pol = Policy() pol.implement_reform(pdict["policy"], raise_errors=False) assert pol.parameter_errors == ''
def test_bad_json_names(tests_path): """ Test that ValueError raised with assump or reform do not end in '.json' """ csvname = os.path.join(tests_path, '..', 'growfactors.csv') with pytest.raises(ValueError): Calculator.read_json_param_objects(csvname, None) with pytest.raises(ValueError): Calculator.read_json_param_objects('http://name.json.html', None) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, csvname) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'http://name.json.html')
def test_read_json_param_and_implement_reform(reform0_file): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method. """ policy = Policy() param_dict = Calculator.read_json_param_objects(reform0_file.name, None) policy.implement_reform(param_dict['policy']) syr = policy.start_year assert syr == 2017 rebate_ceiling = policy._rebate_ceiling
def test_bad_json_names(tests_path): """ Test that ValueError raised with assump or reform do not end in '.json' """ csvname = os.path.join(tests_path, '..', 'growfactors.csv') with pytest.raises(ValueError): Calculator.read_json_param_objects(csvname, None) with pytest.raises(ValueError): Calculator.read_json_param_objects('http://name.json.html', None) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, csvname) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'http://name.json.html')
def test_translate_json_reform_suffixes_mars_indexed(): # test read_json_param_objects() # using MARS-indexed parameter suffixes json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_STD_single": {"2018": [18000], "2016": [16000]}, "_STD_widow": {"2017": [17000], "2019": [19000]} }}""" assump_json = """{ "consumption": {}, "behavior": {}, "growdiff_baseline": { "_ACPIU": {"2013": [0.01]}, "_AWAGE": {"2013": [0.01]}}, "growdiff_response": {} }""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=assump_json) rdict1 = pdict1['policy'] json2 = """{"policy": { "_STD": {"2016": [[16000.00, 12600.00, 6300.00, 9300.00, 12600.00]], "2017": [[16363.20, 12886.02, 6443.01, 9511.11, 17000.00]], "2018": [[18000.00, 13304.82, 6652.41, 9820.22, 17552.50]], "2019": [[18583.20, 13735.90, 6867.95, 10138.4, 19000.00]]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=assump_json) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_STD' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_STD'], rdict2[year]['_STD'], atol=0.01, rtol=0.0)
def test_translate_json_reform_suffixes_mars_indexed(): # test read_json_param_objects() # using MARS-indexed parameter suffixes json1 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_STD_single": {"2018": [18000], "2016": [16000]}, "_STD_widow": {"2017": [17000], "2019": [19000]} }}""" assump_json = """{ "consumption": {}, "behavior": {}, "growdiff_baseline": { "_ACPIU": {"2013": [0.01]}, "_AWAGE": {"2013": [0.01]}}, "growdiff_response": {} }""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=assump_json) rdict1 = pdict1['policy'] json2 = """{"policy": { "_STD": {"2016": [[16000.00, 12600.00, 6300.00, 9300.00, 12600.00]], "2017": [[16524.80, 13013.28, 6506.64, 9605.04, 17000.00]], "2018": [[18000.00, 13432.31, 6716.15, 9914.32, 17547.40]], "2019": [[18592.20, 13874.23, 6937.11, 10240.50, 19000.00]]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=assump_json) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_STD' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_STD'], rdict2[year]['_STD'], atol=0.01, rtol=0.0)
def __init__(self, input_filename, reform, exact_calculations, emulate_taxsim_2441_logic, output_records): """ SimpleTaxIO class constructor. """ # pylint: disable=too-many-arguments # check that input_filename is a string if not isinstance(input_filename, six.string_types): msg = 'SimpleTaxIO.ctor input_filename is not a string' raise ValueError(msg) # construct output_filename and delete old output file if it exists # ... construct reform extension to output_filename if reform is None: ref = '' self._using_reform_file = True else: # if reform is not None if isinstance(reform, six.string_types): if reform.endswith('.json'): ref = '-{}'.format(reform[:-5]) else: ref = '-{}'.format(reform) self._using_reform_file = True elif isinstance(reform, dict): ref = '' self._using_reform_file = False else: msg = 'SimpleTaxIO.ctor reform is neither None, str, nor dict' raise ValueError(msg) # ... construct whole output_filename self._using_input_file = True self._output_filename = '{}.out-simtax{}'.format(input_filename, ref) if os.path.isfile(self._output_filename): os.remove(self._output_filename) # check for existence of file named input_filename if not os.path.isfile(input_filename): msg = 'INPUT file named {} could not be found' raise ValueError(msg.format(input_filename)) # read input file contents into self._input dictionary self._read_input(input_filename) self.policy = Policy() # implement reform if reform is specified if reform: if self._using_reform_file: param_dict = Calculator.read_json_param_objects(reform, None) r_pol = param_dict['policy'] else: r_pol = reform self.policy.implement_reform(r_pol) # validate input variable values self._validate_input() self.calc = self._calc_object(exact_calculations, emulate_taxsim_2441_logic, output_records)
def test_translate_json_reform_suffixes_idedtype(): """ Test read_json_param_objects method using idedtype-indexed param suffixes. """ # test read_json_param_objects(...) # using idedtype-indexed parameter suffixes json1 = """{"policy": { "_ID_BenefitCap_rt": {"2019": [0.2]}, "_ID_BenefitCap_Switch_medical": {"2019": [false]}, "_ID_BenefitCap_Switch_casualty": {"2019": [false]}, "_ID_BenefitCap_Switch_misc": {"2019": [false]}, "_ID_BenefitCap_Switch_interest": {"2019": [false]}, "_ID_BenefitCap_Switch_charity": {"2019": [false]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_ID_BenefitCap_Switch": { "2019": [[false, true, true, false, false, false, false]] }, "_ID_BenefitCap_rt": {"2019": [0.2]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_ID_BenefitCap_rt' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_ID_BenefitCap_rt'], rdict2[year]['_ID_BenefitCap_rt'], atol=0.01, rtol=0.0) if '_ID_BenefitCap_Switch' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_ID_BenefitCap_Switch'], rdict2[year]['_ID_BenefitCap_Switch'], atol=0.01, rtol=0.0)
def test_translate_json_reform_suffixes_idedtype(): # test read_json_param_objects(...) # using idedtype-indexed parameter suffixes json1 = """{"policy": { "_ID_BenefitCap_rt": {"2019": [0.2]}, "_ID_BenefitCap_Switch_medical": {"2019": [false]}, "_ID_BenefitCap_Switch_casualty": {"2019": [false]}, "_ID_BenefitCap_Switch_misc": {"2019": [false]}, "_ID_BenefitCap_Switch_interest": {"2019": [false]}, "_ID_BenefitCap_Switch_charity": {"2019": [false]}, "_II_em": {"2020": [20000], "2015": [15000]} }}""" pdict1 = Calculator.read_json_param_objects(reform=json1, assump=None) rdict1 = pdict1['policy'] json2 = """{"policy": { "_II_em": {"2020": [20000], "2015": [15000]}, "_ID_BenefitCap_Switch": { "2019": [[false, true, true, false, false, false, false]] }, "_ID_BenefitCap_rt": {"2019": [0.2]} }}""" pdict2 = Calculator.read_json_param_objects(reform=json2, assump=None) rdict2 = pdict2['policy'] assert len(rdict2) == len(rdict1) for year in rdict2.keys(): if '_II_em' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_II_em'], rdict2[year]['_II_em'], atol=0.01, rtol=0.0) if '_ID_BenefitCap_rt' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_ID_BenefitCap_rt'], rdict2[year]['_ID_BenefitCap_rt'], atol=0.01, rtol=0.0) if '_ID_BenefitCap_Switch' in rdict2[year].keys(): assert np.allclose(rdict1[year]['_ID_BenefitCap_Switch'], rdict2[year]['_ID_BenefitCap_Switch'], atol=0.01, rtol=0.0)
def test_noreform_documentation(): reform_json = """ { "policy": {} } """ params = Calculator.read_json_param_objects(reform_json, None) assert isinstance(params, dict) actual_doc = Calculator.reform_documentation(params) expected_doc = ('REFORM DOCUMENTATION\n' 'Policy Reform Parameter Values by Year:\n' 'none: using current-law policy parameters\n') assert actual_doc == expected_doc
def test_calc_all(reform_file, rawinputfile): cyr = 2016 pol = Policy() param_dict = Calculator.read_json_param_objects(reform_file.name, None) pol.implement_reform(param_dict['policy']) pol.set_year(cyr) nonstd = Records(data=rawinputfile.name, gfactors=None, weights=None, start_year=cyr) assert nonstd.array_length == RAWINPUTFILE_FUNITS calc = Calculator(policy=pol, records=nonstd, sync_years=False) # keeps raw data unchanged assert calc.current_year == cyr calc.calc_all()
def test_bad_json_names(tests_path): """ Test that ValueError raised with assump or reform do not end in '.json' """ test_url = ('https://raw.githubusercontent.com/PSLmodels/' 'Tax-Calculator/master/taxcalc/reforms/' '2017_law.out.csv') csvname = os.path.join(tests_path, '..', 'growfactors.csv') with pytest.raises(ValueError): Calculator.read_json_param_objects(csvname, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(test_url, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, csvname) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, test_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 = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # 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.policy_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 nonsmall differences if sys.version_info.major == 2: # tighter tests for Python 2.7 mean_msg = differences(mean_path, mean_path[:-4], 'MEAN', small=0.0) corr_msg = differences(corr_path, corr_path[:-4], 'CORR', small=0.0) else: # looser tests for Python 3.6 mean_msg = differences(mean_path, mean_path[:-4], 'MEAN', small=1.0) corr_msg = differences(corr_path, corr_path[:-4], 'CORR', small=0.01) if mean_msg or corr_msg: raise ValueError(mean_msg + corr_msg)
def test_reform_documentation(): """ Test automatic documentation creation. """ reform_json = """ { "policy": { "II_em-indexed": { "2016": false, "2018": true }, "II_em": { "2016": 5000, "2018": 6000, "2020": 7000 }, "EITC_indiv": { "2017": true }, "STD_Aged-indexed": { "2016": false }, "STD_Aged": { "2016": [1600, 1300, 1300, 1600, 1600], "2020": [2000, 2000, 2000, 2000, 2000] }, "ID_BenefitCap_Switch": { "2020": [false, false, false, false, false, false, false] } } } """ assump_json = """ { "consumption": {}, // increase baseline inflation rate by one percentage point in 2014+ // (has no effect on known policy parameter values) "growdiff_baseline": {"ACPIU": {"2014": 0.010}}, "growdiff_response": {"ACPIU": {"2014": 0.015}} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) second_reform = {'II_em': {2019: 6500}} doc = Calculator.reform_documentation(params, [second_reform]) assert isinstance(doc, str) dump = False # set to True to print documentation and force test failure if dump: print(doc) assert 1 == 2
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 = """ { "policy": { "_FICA_ss_trt": { "2018": [0.130], "2020": [0.140] }, "_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 = Calculator.read_json_param_objects(reform_str, None) params_url = Calculator.read_json_param_objects(reform_url, None) assert params_str == params_url
def test_reform_documentation(): """ Test automatic documentation creation. """ reform_json = """ { "II_em-indexed": { "2016": false, "2018": true }, "II_em": { "2016": 5000, "2018": 6000, "2020": 7000 }, "EITC_indiv": { "2017": true }, "STD_Aged-indexed": { "2016": false }, "STD_Aged": { "2016": [1600, 1300, 1300, 1600, 1600], "2020": [2000, 2000, 2000, 2000, 2000] }, "ID_BenefitCap_Switch": { "2020": [false, false, false, false, false, false, false] } } """ assump_json = """ { "consumption": {}, // increase baseline inflation rate by one percentage point in 2014+ // (has no effect on known policy parameter values) "growdiff_baseline": {"ACPIU": {"2014": 0.010}}, "growdiff_response": {"ACPIU": {"2014": 0.015}} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) second_reform = {'II_em': {2019: 6500}} doc = Calculator.reform_documentation(params, [second_reform]) assert isinstance(doc, str) dump = False # set to True to print documentation and force test failure if dump: print(doc) assert 1 == 2
def test_calc_all(): """ Test calc_all method. """ cyr = 2016 pol = Policy() param_dict = Calculator.read_json_param_objects(REFORM_JSON, None) pol.implement_reform(param_dict['policy']) pol.set_year(cyr) nonstd = Records(data=pd.read_csv(StringIO(RAWINPUT_CONTENTS)), start_year=cyr, gfactors=None, weights=None) assert nonstd.array_length == RAWINPUT_FUNITS calc = Calculator(policy=pol, records=nonstd, sync_years=False) # keeps raw data unchanged assert calc.current_year == cyr assert calc.reform_warnings == ''
def test_calc_all(reform_file, rawinputfile): """ Test calc_all method. """ cyr = 2016 pol = Policy() param_dict = Calculator.read_json_param_objects(reform_file.name, None) pol.implement_reform(param_dict['policy']) pol.set_year(cyr) nonstd = Records(data=rawinputfile.name, gfactors=None, weights=None, start_year=cyr) assert nonstd.array_length == RAWINPUTFILE_FUNITS calc = Calculator(policy=pol, records=nonstd, sync_years=False) # keeps raw data unchanged assert calc.current_year == cyr assert calc.reform_warnings == ''
def test_reform_documentation(): """ Test automatic documentation creation. """ reform_json = """ { "policy": { "_II_em_cpi": {"2016": false, "2018": true}, "_II_em": {"2016": [5000], "2018": [6000], "2020": [7000]}, "_EITC_indiv": {"2017": [true]}, "_STD_Aged_cpi": {"2016": false}, "_STD_Aged": {"2016": [[1600, 1300, 1300, 1600, 1600]], "2020": [[2000, 2000, 2000, 2000, 2000]]}, "_ID_BenefitCap_Switch_medical": {"2020": [false]}, "_ID_BenefitCap_Switch_casualty": {"2020": [false]}, "_ID_BenefitCap_Switch_misc": {"2020": [false]}, "_ID_BenefitCap_Switch_interest": {"2020": [false]}, "_ID_BenefitCap_Switch_charity": {"2020": [false]} } } """ assump_json = """ { "consumption": {}, // increase baseline inflation rate by one percentage point in 2014+ // (has no effect on known policy parameter values) "growdiff_baseline": {"_ACPIU": {"2014": [0.01]}}, "growdiff_response": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) doc = Calculator.reform_documentation(params) assert isinstance(doc, str) dump = False # set to True to print documentation and force test failure if dump: print(doc) assert 1 == 2
def test_read_json_reform_file_and_implement_reform(reform_file, assump_file, set_year): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method and Calculate.calc_all() NOTE: implement_reform called when policy.current_year == policy.start_year """ pol = Policy() if set_year: pol.set_year(2015) param_dict = Calculator.read_json_param_objects(reform_file.name, assump_file.name) pol.implement_reform(param_dict['policy']) syr = pol.start_year # pylint: disable=protected-access,no-member 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 test_read_json_reform_file_and_implement_reform(reform_file, assump_file, set_year): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method and Calculate.calc_all() NOTE: implement_reform called when policy.current_year == policy.start_year """ pol = Policy() if set_year: pol.set_year(2015) param_dict = Calculator.read_json_param_objects(reform_file.name, assump_file.name) pol.implement_reform(param_dict['policy']) syr = pol.start_year 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 test_reform_documentation(): reform_json = """ { "policy": { "_II_em_cpi": {"2016": false, "2018": true}, "_II_em": {"2016": [5000], "2018": [6000], "2020": [7000]}, "_EITC_indiv": {"2017": [true]}, "_STD_Aged_cpi": {"2016": false}, "_STD_Aged": {"2016": [[1600, 1300, 1300, 1600, 1600]], "2020": [[2000, 2000, 2000, 2000, 2000]]}, "_ID_BenefitCap_Switch_medical": {"2020": [false]}, "_ID_BenefitCap_Switch_casualty": {"2020": [false]}, "_ID_BenefitCap_Switch_misc": {"2020": [false]}, "_ID_BenefitCap_Switch_interest": {"2020": [false]}, "_ID_BenefitCap_Switch_charity": {"2020": [false]} } } """ assump_json = """ { "consumption": {}, "behavior": {}, // increase baseline inflation rate by one percentage point in 2014+ // (has no effect on known policy parameter values) "growdiff_baseline": {"_ACPIU": {"2014": [0.01]}}, "growdiff_response": {}, "growmodel": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) assert isinstance(params, dict) doc = Calculator.reform_documentation(params) assert isinstance(doc, str) dump = False # set to True to print documentation and force test failure if dump: print(doc) assert 1 == 2
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 = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # 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 nonsmall 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_bad_json_assump_file(): """ Test invalid JSON assumption files. """ badassump1 = """ { "consumption": { // example of incorrect JSON because 'x' must be "x" 'x': {"2014": 0.25} }, "growdiff_baseline": {}, "growdiff_response": {} } """ with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump1) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_read_bad_json_assump_file(): """ Test invalid JSON assumption files. """ badassump1 = """ { "consumption": { // example of incorrect JSON because 'x' must be "x" 'x': {"2014": 0.25} }, "growdiff_baseline": {}, "growdiff_response": {} } """ with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump1) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_behavioral_response(puf_subsample): """ Test that behavioral-response results are the same when generated from standard Tax-Calculator calls and when generated from tbi.run_nth_year_taxcalc_model() calls """ # specify reform and assumptions reform_json = """ {"policy": { "_II_rt5": {"2020": [0.25]}, "_II_rt6": {"2020": [0.25]}, "_II_rt7": {"2020": [0.25]}, "_PT_rt5": {"2020": [0.25]}, "_PT_rt6": {"2020": [0.25]}, "_PT_rt7": {"2020": [0.25]}, "_II_em": {"2020": [1000]} }} """ assump_json = """ {"behavior": {"_BE_sub": {"2013": [0.25]}}, "growdiff_baseline": {}, "growdiff_response": {}, "consumption": {}, "growmodel": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) # specify keyword arguments used in tbi function call kwargs = { 'start_year': 2019, 'year_n': 0, 'use_puf_not_cps': True, 'use_full_sample': False, 'user_mods': { 'policy': params['policy'], 'behavior': params['behavior'], 'growdiff_baseline': params['growdiff_baseline'], 'growdiff_response': params['growdiff_response'], 'consumption': params['consumption'], 'growmodel': params['growmodel'] }, 'return_dict': False } # generate aggregate results two ways: using tbi and standard calls num_years = 9 std_res = dict() tbi_res = dict() for using_tbi in [True, False]: for year in range(0, num_years): cyr = year + kwargs['start_year'] if using_tbi: kwargs['year_n'] = year tables = run_nth_year_taxcalc_model(**kwargs) tbi_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi_res[cyr][tbl] = tables[tbl] else: rec = Records(data=puf_subsample) pol = Policy() calc1 = Calculator(policy=pol, records=rec) pol.implement_reform(params['policy']) assert not pol.parameter_errors beh = Behavior() beh.update_behavior(params['behavior']) calc2 = Calculator(policy=pol, records=rec, behavior=beh) assert calc2.behavior_has_response() calc1.advance_to_year(cyr) calc2.advance_to_year(cyr) calc2 = Behavior.response(calc1, calc2) std_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: if tbl.endswith('_1'): itax = calc1.weighted_total('iitax') ptax = calc1.weighted_total('payrolltax') ctax = calc1.weighted_total('combined') elif tbl.endswith('_2'): itax = calc2.weighted_total('iitax') ptax = calc2.weighted_total('payrolltax') ctax = calc2.weighted_total('combined') elif tbl.endswith('_d'): itax = (calc2.weighted_total('iitax') - calc1.weighted_total('iitax')) ptax = (calc2.weighted_total('payrolltax') - calc1.weighted_total('payrolltax')) ctax = (calc2.weighted_total('combined') - calc1.weighted_total('combined')) cols = ['0_{}'.format(year)] rows = ['ind_tax', 'payroll_tax', 'combined_tax'] datalist = [itax, ptax, ctax] std_res[cyr][tbl] = pd.DataFrame(data=datalist, index=rows, columns=cols) # compare the two sets of results # NOTE that the tbi results have been "fuzzed" for PUF privacy reasons, # so there is no expectation that the results should be identical. no_diffs = True reltol = 0.004 # std and tbi differ if more than 0.4 percent different for year in range(0, num_years): cyr = year + kwargs['start_year'] col = '0_{}'.format(year) for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi = tbi_res[cyr][tbl][col] std = std_res[cyr][tbl][col] if not np.allclose(tbi, std, atol=0.0, rtol=reltol): no_diffs = False print('**** DIFF for year {} (year_n={}):'.format(cyr, year)) print('TBI RESULTS:') print(tbi) print('STD RESULTS:') print(std) assert no_diffs
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 reform_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 text file. """ # pylint: disable=too-many-statements,too-many-locals used_dist_stats = [ 'c00100', # AGI 'c04600', # personal exemptions 'standard', # standard deduction 'c04800', # regular taxable income 'c05800', # income tax before credits 'iitax', # income tax after credits 'payrolltax', # payroll taxes 'aftertax_income' ] # aftertax expanded income unused_dist_stats = set(DIST_TABLE_COLUMNS) - set(used_dist_stats) renamed_columns = { 'c00100': 'AGI', 'c04600': 'pexempt', 'standard': 'stdded', 'c04800': 'taxinc', 'c05800': 'tax-wo-credits', 'iitax': 'inctax', 'payrolltax': 'paytax', 'aftertax_income': 'ataxinc' } # embedded function used only in test_reform_json_and_output def write_distribution_table(calc, resfilename): """ Write abbreviated distribution table calc to file with resfilename. """ dist, _ = calc.distribution_tables(None, groupby='large_income_bins') for stat in unused_dist_stats: del dist[stat] dist = dist[used_dist_stats] dist.rename(mapper=renamed_columns, axis='columns', inplace=True) pd.options.display.float_format = '{:7.0f}'.format with open(resfilename, 'w') as resfile: dist.to_string(resfile) # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res and base.out file contents are the same; return False if base.res and base.out file contents differ. """ with open(base + '.res') as resfile: act_res = resfile.read() with open(base + '.out') as outfile: exp_res = outfile.read() # check to see if act_res & exp_res have differences return not nonsmall_diffs(act_res.splitlines(True), exp_res.splitlines(True)) # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv') cases = Records( data=cases_path, gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None, start_year=tax_year) # set raw input data year # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc1 = Calculator(policy=Policy(), records=cases, verbose=False) calc1.advance_to_year(tax_year) calc1.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res') write_distribution_table(calc1, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) # 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 = Calculator.read_json_param_objects(pre_tcja_jrf, None) # 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 = Calculator.read_json_param_objects(jrf_text, None) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja['policy']) pol.implement_reform(reform['policy']) assert not pol.reform_errors calc2 = Calculator(policy=pol, records=cases, verbose=False) calc2.advance_to_year(tax_year) calc2.calc_all() res_path = jrf.replace('.json', '.res') write_distribution_table(calc2, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) 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 test_behavioral_response(use_puf_not_cps, puf_subsample, cps_fullsample): """ Test that behavioral-response results are the same when generated from standard Tax-Calculator calls and when generated from tbi.run_nth_year_taxcalc_model() calls """ # specify reform and assumptions reform_json = """ {"policy": { "_II_rt5": {"2020": [0.25]}, "_II_rt6": {"2020": [0.25]}, "_II_rt7": {"2020": [0.25]}, "_PT_rt5": {"2020": [0.25]}, "_PT_rt6": {"2020": [0.25]}, "_PT_rt7": {"2020": [0.25]}, "_II_em": {"2020": [1000]} }} """ assump_json = """ {"behavior": {"_BE_sub": {"2013": [0.25]}}, "growdiff_baseline": {}, "growdiff_response": {}, "consumption": {}, "growmodel": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) # specify keyword arguments used in tbi function call kwargs = { 'start_year': 2019, 'year_n': 0, 'use_puf_not_cps': use_puf_not_cps, 'use_full_sample': False, 'user_mods': { 'policy': params['policy'], 'behavior': params['behavior'], 'growdiff_baseline': params['growdiff_baseline'], 'growdiff_response': params['growdiff_response'], 'consumption': params['consumption'], 'growmodel': params['growmodel'] }, 'return_dict': False } # generate aggregate results two ways: using tbi and standard calls num_years = 9 std_res = dict() tbi_res = dict() if use_puf_not_cps: rec = Records(data=puf_subsample) else: # IMPORTANT: must use same subsample as used in test_cpscsv.py because # that is the subsample used by run_nth_year_taxcalc_model std_cps_subsample = cps_fullsample.sample(frac=0.03, random_state=180) rec = Records.cps_constructor(data=std_cps_subsample) for using_tbi in [True, False]: for year in range(0, num_years): cyr = year + kwargs['start_year'] if using_tbi: kwargs['year_n'] = year tables = run_nth_year_taxcalc_model(**kwargs) tbi_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi_res[cyr][tbl] = tables[tbl] else: pol = Policy() calc1 = Calculator(policy=pol, records=rec) pol.implement_reform(params['policy']) assert not pol.parameter_errors beh = Behavior() beh.update_behavior(params['behavior']) calc2 = Calculator(policy=pol, records=rec, behavior=beh) assert calc2.behavior_has_response() calc1.advance_to_year(cyr) calc2.advance_to_year(cyr) calc2 = Behavior.response(calc1, calc2) std_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: if tbl.endswith('_1'): itax = calc1.weighted_total('iitax') ptax = calc1.weighted_total('payrolltax') ctax = calc1.weighted_total('combined') elif tbl.endswith('_2'): itax = calc2.weighted_total('iitax') ptax = calc2.weighted_total('payrolltax') ctax = calc2.weighted_total('combined') elif tbl.endswith('_d'): itax = (calc2.weighted_total('iitax') - calc1.weighted_total('iitax')) ptax = (calc2.weighted_total('payrolltax') - calc1.weighted_total('payrolltax')) ctax = (calc2.weighted_total('combined') - calc1.weighted_total('combined')) cols = ['0_{}'.format(year)] rows = ['ind_tax', 'payroll_tax', 'combined_tax'] datalist = [itax, ptax, ctax] std_res[cyr][tbl] = pd.DataFrame(data=datalist, index=rows, columns=cols) for col in std_res[cyr][tbl].columns: val = std_res[cyr][tbl][col] * 1e-9 std_res[cyr][tbl][col] = round(val, 3) # compare the two sets of results # NOTE that the PUF tbi results have been "fuzzed" for privacy reasons, # so there is no expectation that those results should be identical. no_diffs = True cps_dump = False # setting to True produces dump output and test failure if use_puf_not_cps: reltol = 0.004 # std and tbi differ if more than 0.4 percent different dataset = 'PUF' dumping = False else: # CPS results are not "fuzzed", so reltol = 1e-9 # std and tbi should be virtually identical dataset = 'CPS' dumping = cps_dump for year in range(0, num_years): cyr = year + kwargs['start_year'] do_dump = bool(dumping and cyr >= 2019 and cyr <= 2020) col = '0_{}'.format(year) for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi = tbi_res[cyr][tbl][col] if do_dump: txt = 'DUMP of {} {} table for year {}:' print(txt.format(dataset, tbl, cyr)) print(tbi) std = std_res[cyr][tbl][col] if not np.allclose(tbi, std, atol=0.0, rtol=reltol): no_diffs = False txt = '***** {} diff in {} table for year {} (year_n={}):' print(txt.format(dataset, tbl, cyr, year)) print('TBI RESULTS:') print(tbi) print('STD RESULTS:') print(std) assert no_diffs assert not dumping
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 = Calculator.read_json_param_objects(pre_tcja_jrf, None) # 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 = Calculator.read_json_param_objects(jrf_text, None) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja['policy']) assert not pol.parameter_errors pol.implement_reform(reform['policy']) 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 run_micro_macro(user_params): # Grab a reform JSON file already in Tax-Calculator # In this example the 'reform' is a change to 2017 law (the # baseline policy is tax law in 2018) reform_url = ('https://raw.githubusercontent.com/' 'PSLmodels/Tax-Calculator/master/taxcalc/' 'reforms/2017_law.json') ref = Calculator.read_json_param_objects(reform_url, None) reform = ref['policy'] # Define parameters to use for multiprocessing client = Client(processes=False) num_workers = 1 # multiprocessing.cpu_count() print('Number of workers = ', num_workers) start_time = time.time() # Set some model parameters # See parameters.py for description of these parameters alpha_T = np.zeros(50) alpha_T[0:2] = 0.09 alpha_T[2:10] = 0.09 + 0.01 alpha_T[10:40] = 0.09 - 0.01 alpha_T[40:] = 0.09 alpha_G = np.zeros(7) alpha_G[0:3] = 0.05 - 0.01 alpha_G[3:6] = 0.05 - 0.005 alpha_G[6:] = 0.05 small_open = False user_params = {'frisch': 0.41, 'start_year': 2018, 'tau_b': [(0.21 * 0.55) * (0.017 / 0.055), (0.21 * 0.55) * (0.017 / 0.055)], 'debt_ratio_ss': 1.0, 'alpha_T': alpha_T.tolist(), 'alpha_G': alpha_G.tolist(), 'small_open': small_open} ''' ------------------------------------------------------------------------ Run baseline policy first ------------------------------------------------------------------------ ''' output_base = BASELINE_DIR kwargs = {'output_base': output_base, 'baseline_dir': BASELINE_DIR, 'test': False, 'time_path': True, 'baseline': True, 'user_params': user_params, 'guid': '_example', 'run_micro': True, 'data': 'cps', 'client': client, 'num_workers': num_workers} start_time = time.time() runner(**kwargs) print('run time = ', time.time()-start_time) ''' ------------------------------------------------------------------------ Run reform policy ------------------------------------------------------------------------ ''' user_params = {'frisch': 0.41, 'start_year': 2018, 'tau_b': [(0.35 * 0.55) * (0.017 / 0.055)], 'debt_ratio_ss': 1.0, 'alpha_T': alpha_T.tolist(), 'alpha_G': alpha_G.tolist(), 'small_open': small_open} output_base = REFORM_DIR kwargs = {'output_base': output_base, 'baseline_dir': BASELINE_DIR, 'test': False, 'time_path': True, 'baseline': False, 'user_params': user_params, 'guid': '_example', 'reform': reform, 'run_micro': True, 'data': 'cps', 'client': client, 'num_workers': num_workers} start_time = time.time() runner(**kwargs) print('run time = ', time.time()-start_time) # return ans - the percentage changes in macro aggregates and prices # due to policy changes from the baseline to the reform ans = postprocess.create_diff( baseline_dir=BASELINE_DIR, policy_dir=REFORM_DIR) print("total time was ", (time.time() - start_time)) print('Percentage changes in aggregates:', ans)
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() reform = Calculator.read_json_param_objects(rtext, None) pol.implement_reform(reform['policy']) # 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() reform = Calculator.read_json_param_objects(rtext, None) pol.implement_reform(reform['policy']) # 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_json_assump_url(): """ Test reading JSON assumption file using URL. """ assump_str = """ { "consumption": { // all BEN_*_value parameters have a default value of one "BEN_housing_value": {"2017": 1.0}, "BEN_snap_value": {"2017": 1.0}, "BEN_tanf_value": {"2017": 1.0}, "BEN_vet_value": {"2017": 1.0}, "BEN_wic_value": {"2017": 1.0}, "BEN_mcare_value": {"2017": 1.0}, "BEN_mcaid_value": {"2017": 1.0}, "BEN_other_value": {"2017": 1.0}, // all MPC_* parameters have a default value of zero "MPC_e17500": {"2017": 0.0}, "MPC_e18400": {"2017": 0.0}, "MPC_e19800": {"2017": 0.0}, "MPC_e20400": {"2017": 0.0} }, "growdiff_baseline": { // all growdiff_baseline parameters have a default value of zero "ABOOK": {"2017": 0.0}, "ACGNS": {"2017": 0.0}, "ACPIM": {"2017": 0.0}, "ACPIU": {"2017": 0.0}, "ADIVS": {"2017": 0.0}, "AINTS": {"2017": 0.0}, "AIPD": {"2017": 0.0}, "ASCHCI": {"2017": 0.0}, "ASCHCL": {"2017": 0.0}, "ASCHEI": {"2017": 0.0}, "ASCHEL": {"2017": 0.0}, "ASCHF": {"2017": 0.0}, "ASOCSEC": {"2017": 0.0}, "ATXPY": {"2017": 0.0}, "AUCOMP": {"2017": 0.0}, "AWAGE": {"2017": 0.0}, "ABENOTHER": {"2017": 0.0}, "ABENMCARE": {"2017": 0.0}, "ABENMCAID": {"2017": 0.0}, "ABENSSI": {"2017": 0.0}, "ABENSNAP": {"2017": 0.0}, "ABENWIC": {"2017": 0.0}, "ABENHOUSING": {"2017": 0.0}, "ABENTANF": {"2017": 0.0}, "ABENVET": {"2017": 0.0} }, "growdiff_response": { // all growdiff_response parameters have a default value of zero "ABOOK": {"2017": 0.0}, "ACGNS": {"2017": 0.0}, "ACPIM": {"2017": 0.0}, "ACPIU": {"2017": 0.0}, "ADIVS": {"2017": 0.0}, "AINTS": {"2017": 0.0}, "AIPD": {"2017": 0.0}, "ASCHCI": {"2017": 0.0}, "ASCHCL": {"2017": 0.0}, "ASCHEI": {"2017": 0.0}, "ASCHEL": {"2017": 0.0}, "ASCHF": {"2017": 0.0}, "ASOCSEC": {"2017": 0.0}, "ATXPY": {"2017": 0.0}, "AUCOMP": {"2017": 0.0}, "AWAGE": {"2017": 0.0}, "ABENOTHER": {"2017": 0.0}, "ABENMCARE": {"2017": 0.0}, "ABENMCAID": {"2017": 0.0}, "ABENSSI": {"2017": 0.0}, "ABENSNAP": {"2017": 0.0}, "ABENWIC": {"2017": 0.0}, "ABENHOUSING": {"2017": 0.0}, "ABENTANF": {"2017": 0.0}, "ABENVET": {"2017": 0.0} } } """ assump_url = ('https://raw.githubusercontent.com/PSLmodels/' 'Tax-Calculator/master/taxcalc/assumptions/' 'economic_assumptions_template.json') params_str = Calculator.read_json_param_objects(None, assump_str) assert params_str params_url = Calculator.read_json_param_objects(None, assump_url) assert params_url assert params_url == params_str
def test_agg(tests_path, cps_fullsample): """ Test current-law aggregate taxes using cps.csv file. """ # pylint: disable=too-many-statements,too-many-locals nyrs = 10 # create a baseline Policy object containing 2017_law.json parameters pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # create a Records object (rec) containing all cps.csv input records recs = Records.cps_constructor(data=cps_fullsample) # create a Calculator object using baseline policy and cps records calc = Calculator(policy=baseline_policy, records=recs) calc_start_year = calc.current_year # create aggregate diagnostic table (adt) as a Pandas DataFrame object adt = calc.diagnostic_table(nyrs) taxes_fullsample = adt.loc["Combined Liability ($b)"] # convert adt to a string with a trailing EOL character actual_results = adt.to_string(float_format='%8.1f') + '\n' # read expected results from file aggres_path = os.path.join(tests_path, 'cpscsv_agg_expect.txt') with open(aggres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt # ensure actual and expected results have no nonsmall differences diffs = nonsmall_diffs(actual_results.splitlines(True), expected_results.splitlines(True)) if diffs: new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(actual_results) msg = 'CPSCSV AGG RESULTS DIFFER\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN cpscsv_agg_actual.txt FILE ---\n' msg += '--- if new OK, copy cpscsv_agg_actual.txt to ---\n' msg += '--- cpscsv_agg_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) # create aggregate diagnostic table using unweighted sub-sample of records rn_seed = 180 # to ensure sub-sample is always the same subfrac = 0.03 # sub-sample fraction subsample = cps_fullsample.sample(frac=subfrac, random_state=rn_seed) recs_subsample = Records.cps_constructor(data=subsample) calc_subsample = Calculator(policy=baseline_policy, records=recs_subsample) adt_subsample = calc_subsample.diagnostic_table(nyrs) # compare combined tax liability from full and sub samples for each year taxes_subsample = adt_subsample.loc["Combined Liability ($b)"] msg = '' for cyr in range(calc_start_year, calc_start_year + nyrs): if cyr == calc_start_year: reltol = 0.014 else: reltol = 0.006 if not np.allclose(taxes_subsample[cyr], taxes_fullsample[cyr], atol=0.0, rtol=reltol): reldiff = (taxes_subsample[cyr] / taxes_fullsample[cyr]) - 1. line1 = '\nCPSCSV AGG SUB-vs-FULL RESULTS DIFFER IN {}' line2 = '\n when subfrac={:.3f}, rtol={:.4f}, seed={}' line3 = '\n with sub={:.3f}, full={:.3f}, rdiff={:.4f}' msg += line1.format(cyr) msg += line2.format(subfrac, reltol, rn_seed) msg += line3.format(taxes_subsample[cyr], taxes_fullsample[cyr], reldiff) if msg: raise ValueError(msg)
def test_read_bad_json_reform_file(bad1reformfile, bad2reformfile, bad3reformfile): """ Test invalid JSON reform files. """ with pytest.raises(ValueError): Calculator.read_json_param_objects(bad1reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(bad2reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(bad3reformfile.name, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(list(), None) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_read_bad_json_assump_file(): """ Test invalid JSON assumption files. """ badassump1 = """ { "consumption": { // example of incorrect JSON because 'x' must be "x" 'x': {"2014": 0.25} }, "growdiff_baseline": {}, "growdiff_response": {} } """ badassump2 = """ { "consumptionx": {}, // example of file not containing "consumption" key "growdiff_baseline": {}, "growdiff_response": {} } """ badassump3 = """ { "consumption": {}, "growdiff_baseline": {}, "growdiff_response": {}, "policy": { // example of misplaced policy key "SS_Earnings_c": {"2018": 9e99} } } """ badassump4 = """ { "consumption": {}, "growdiff_baseline": {}, "growdiff_response": {}, "illegal_key": {} } """ with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump1) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump2) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump3) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, badassump4) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
def test_read_bad_json_reform_file(): """ Test invalid JSON reform files. """ badreform1 = """ { "policy": { // example of incorrect JSON because 'x' must be "x" 'x': {"2014": 4000} } } """ badreform2 = """ { "title": "", "policyx": { // example of reform file not containing "policy" key "SS_Earnings_c": {"2018": 9e99} } } """ badreform3 = """ { "title": "", "policy": { "SS_Earnings_c": {"2018": 9e99} }, "consumption": { // example of misplaced "consumption" key } } """ with pytest.raises(ValueError): Calculator.read_json_param_objects(badreform1, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(badreform2, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(badreform3, None) with pytest.raises(ValueError): Calculator.read_json_param_objects(list(), None) with pytest.raises(ValueError): Calculator.read_json_param_objects(None, 'unknown_file_name') with pytest.raises(ValueError): Calculator.read_json_param_objects(None, list())
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 text file. """ # pylint: disable=too-many-statements,too-many-locals used_dist_stats = ['c00100', # AGI 'c04600', # personal exemptions 'standard', # standard deduction 'c04800', # regular taxable income 'c05800', # income tax before credits 'iitax', # income tax after credits 'payrolltax', # payroll taxes 'aftertax_income'] # aftertax expanded income unused_dist_stats = set(DIST_TABLE_COLUMNS) - set(used_dist_stats) renamed_columns = {'c00100': 'AGI', 'c04600': 'pexempt', 'standard': 'stdded', 'c04800': 'taxinc', 'c05800': 'tax-wo-credits', 'iitax': 'inctax', 'payrolltax': 'paytax', 'aftertax_income': 'ataxinc'} # embedded function used only in test_reform_json_and_output def write_distribution_table(calc, resfilename): """ Write abbreviated distribution table calc to file with resfilename. """ dist, _ = calc.distribution_tables(None, 'standard_income_bins', scaling=False) for stat in unused_dist_stats: del dist[stat] dist = dist[used_dist_stats] dist.rename(mapper=renamed_columns, axis='columns', inplace=True) with open(resfilename, 'w') as resfile: dist.to_string(resfile, float_format='%7.0f') # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res and base.out file contents are the same; return False if base.res and base.out file contents differ. """ with open(base + '.res') as resfile: act_res = resfile.read() with open(base + '.out') as outfile: exp_res = outfile.read() # check to see if act_res & exp_res have differences return not nonsmall_diffs(act_res.splitlines(True), exp_res.splitlines(True)) # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv') cases = Records(data=cases_path, gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None, start_year=tax_year) # set raw input data year # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc1 = Calculator(policy=Policy(), records=cases, verbose=False) calc1.advance_to_year(tax_year) calc1.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res') write_distribution_table(calc1, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) # 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 = Calculator.read_json_param_objects(pre_tcja_jrf, None) # 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 = Calculator.read_json_param_objects(jrf_text, None) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja['policy']) pol.implement_reform(reform['policy']) assert not pol.parameter_errors calc2 = Calculator(policy=pol, records=cases, verbose=False) calc2.advance_to_year(tax_year) calc2.calc_all() res_path = jrf.replace('.json', '.res') write_distribution_table(calc2, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) 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 test_json_assump_url(): """ Test reading JSON assumption file using URL. """ assump_str = """ { "consumption": { "_BEN_housing_value": {"2017": [1.0]}, "_BEN_snap_value": {"2017": [1.0]}, "_BEN_tanf_value": {"2017": [1.0]}, "_BEN_vet_value": {"2017": [1.0]}, "_BEN_wic_value": {"2017": [1.0]}, "_BEN_mcare_value": {"2017": [1.0]}, "_BEN_mcaid_value": {"2017": [1.0]}, "_BEN_other_value": {"2017": [1.0]}, "_MPC_e17500": {"2017": [0.0]}, "_MPC_e18400": {"2017": [0.0]}, "_MPC_e19800": {"2017": [0.0]}, "_MPC_e20400": {"2017": [0.0]} }, "growdiff_baseline": { "_ABOOK": {"2017": [0.0]}, "_ACGNS": {"2017": [0.0]}, "_ACPIM": {"2017": [0.0]}, "_ACPIU": {"2017": [0.0]}, "_ADIVS": {"2017": [0.0]}, "_AINTS": {"2017": [0.0]}, "_AIPD": {"2017": [0.0]}, "_ASCHCI": {"2017": [0.0]}, "_ASCHCL": {"2017": [0.0]}, "_ASCHEI": {"2017": [0.0]}, "_ASCHEL": {"2017": [0.0]}, "_ASCHF": {"2017": [0.0]}, "_ASOCSEC": {"2017": [0.0]}, "_ATXPY": {"2017": [0.0]}, "_AUCOMP": {"2017": [0.0]}, "_AWAGE": {"2017": [0.0]}, "_ABENOTHER": {"2017": [0.0]}, "_ABENMCARE": {"2017": [0.0]}, "_ABENMCAID": {"2017": [0.0]}, "_ABENSSI": {"2017": [0.0]}, "_ABENSNAP": {"2017": [0.0]}, "_ABENWIC": {"2017": [0.0]}, "_ABENHOUSING": {"2017": [0.0]}, "_ABENTANF": {"2017": [0.0]}, "_ABENVET": {"2017": [0.0]} }, "growdiff_response": { "_ABOOK": {"2017": [0.0]}, "_ACGNS": {"2017": [0.0]}, "_ACPIM": {"2017": [0.0]}, "_ACPIU": {"2017": [0.0]}, "_ADIVS": {"2017": [0.0]}, "_AINTS": {"2017": [0.0]}, "_AIPD": {"2017": [0.0]}, "_ASCHCI": {"2017": [0.0]}, "_ASCHCL": {"2017": [0.0]}, "_ASCHEI": {"2017": [0.0]}, "_ASCHEL": {"2017": [0.0]}, "_ASCHF": {"2017": [0.0]}, "_ASOCSEC": {"2017": [0.0]}, "_ATXPY": {"2017": [0.0]}, "_AUCOMP": {"2017": [0.0]}, "_AWAGE": {"2017": [0.0]}, "_ABENOTHER": {"2017": [0.0]}, "_ABENMCARE": {"2017": [0.0]}, "_ABENMCAID": {"2017": [0.0]}, "_ABENSSI": {"2017": [0.0]}, "_ABENSNAP": {"2017": [0.0]}, "_ABENWIC": {"2017": [0.0]}, "_ABENHOUSING": {"2017": [0.0]}, "_ABENTANF": {"2017": [0.0]}, "_ABENVET": {"2017": [0.0]} } } """ assump_url = ('https://raw.githubusercontent.com/PSLmodels/' 'Tax-Calculator/master/taxcalc/assumptions/' 'economic_assumptions_template.json') params_str = Calculator.read_json_param_objects(None, assump_str) assert params_str params_url = Calculator.read_json_param_objects(None, assump_url) assert params_str == params_url
def test_agg(tests_path, puf_fullsample): """ Test Tax-Calculator aggregate taxes with no policy reform using the full-sample puf.csv and a small sub-sample of puf.csv """ # pylint: disable=too-many-locals,too-many-statements nyrs = 10 # create a baseline Policy object containing 2017_law.json parameters pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # create a Records object (rec) containing all puf.csv input records rec = Records(data=puf_fullsample) # create a Calculator object using baseline policy and puf records calc = Calculator(policy=baseline_policy, records=rec) calc_start_year = calc.current_year # create aggregate diagnostic table (adt) as a Pandas DataFrame object adt = calc.diagnostic_table(nyrs) taxes_fullsample = adt.loc["Combined Liability ($b)"] # convert adt results to a string with a trailing EOL character adtstr = adt.to_string() + '\n' # create actual and expected lists of diagnostic table lines actual = adtstr.splitlines(True) aggres_path = os.path.join(tests_path, 'pufcsv_agg_expect.txt') with open(aggres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt expect = expected_results.splitlines(True) # ensure actual and expect lines have differences no more than small value if sys.version_info.major == 2: small = 0.0 # tighter test for Python 2.7 else: small = 0.1 # looser test for Python 3.6 diffs = nonsmall_diffs(actual, expect, small) if diffs: new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(adtstr) msg = 'PUFCSV AGG RESULTS DIFFER FOR FULL-SAMPLE\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN pufcsv_agg_actual.txt FILE ---\n' msg += '--- if new OK, copy pufcsv_agg_actual.txt to ---\n' msg += '--- pufcsv_agg_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) # create aggregate diagnostic table using unweighted sub-sample of records fullsample = puf_fullsample rn_seed = 180 # to ensure sub-sample is always the same subfrac = 0.05 # sub-sample fraction subsample = fullsample.sample(frac=subfrac, random_state=rn_seed) rec_subsample = Records(data=subsample) calc_subsample = Calculator(policy=baseline_policy, records=rec_subsample) adt_subsample = calc_subsample.diagnostic_table(nyrs) # compare combined tax liability from full and sub samples for each year taxes_subsample = adt_subsample.loc["Combined Liability ($b)"] reltol = 0.01 # maximum allowed relative difference in tax liability if not np.allclose(taxes_subsample, taxes_fullsample, atol=0.0, rtol=reltol): msg = 'PUFCSV AGG RESULTS DIFFER IN SUB-SAMPLE AND FULL-SAMPLE\n' msg += 'WHEN subfrac={:.3f}, rtol={:.4f}, seed={}\n'.format(subfrac, reltol, rn_seed) it_sub = np.nditer(taxes_subsample, flags=['f_index']) it_all = np.nditer(taxes_fullsample, flags=['f_index']) while not it_sub.finished: cyr = it_sub.index + calc_start_year tax_sub = float(it_sub[0]) tax_all = float(it_all[0]) reldiff = abs(tax_sub - tax_all) / abs(tax_all) if reldiff > reltol: msgstr = ' year,sub,full,reldiff= {}\t{:.2f}\t{:.2f}\t{:.4f}\n' msg += msgstr.format(cyr, tax_sub, tax_all, reldiff) it_sub.iternext() it_all.iternext() raise ValueError(msg)
def test_read_json_param_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 // Calculator.read_json_param_objects() 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. { "policy": { "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) param_dict = Calculator.read_json_param_objects(reform_json, None) policy.implement_reform(param_dict['policy']) 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_agg(tests_path, puf_fullsample): """ Test Tax-Calculator aggregate taxes with no policy reform using the full-sample puf.csv and a small sub-sample of puf.csv """ # pylint: disable=too-many-locals,too-many-statements nyrs = 10 # create a baseline Policy object containing 2017_law.json parameters pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # create a Records object (rec) containing all puf.csv input records recs = Records(data=puf_fullsample) # create a Calculator object using baseline policy and puf records calc = Calculator(policy=baseline_policy, records=recs) calc_start_year = calc.current_year # create aggregate diagnostic table (adt) as a Pandas DataFrame object adt = calc.diagnostic_table(nyrs) taxes_fullsample = adt.loc["Combined Liability ($b)"] # convert adt results to a string with a trailing EOL character adtstr = adt.to_string(float_format='%8.1f') + '\n' # create actual and expected lists of diagnostic table lines actual = adtstr.splitlines(True) aggres_path = os.path.join(tests_path, 'pufcsv_agg_expect.txt') with open(aggres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt expect = expected_results.splitlines(True) # ensure actual and expect lines have differences no more than small value diffs = nonsmall_diffs(actual, expect) if diffs: new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(adtstr) msg = 'PUFCSV AGG RESULTS DIFFER FOR FULL-SAMPLE\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN pufcsv_agg_actual.txt FILE ---\n' msg += '--- if new OK, copy pufcsv_agg_actual.txt to ---\n' msg += '--- pufcsv_agg_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) # create aggregate diagnostic table using unweighted sub-sample of records fullsample = puf_fullsample rn_seed = 2222 # to ensure sub-sample is always the same subfrac = 0.05 # sub-sample fraction subsample = fullsample.sample(frac=subfrac, random_state=rn_seed) recs_subsample = Records(data=subsample) calc_subsample = Calculator(policy=baseline_policy, records=recs_subsample) adt_subsample = calc_subsample.diagnostic_table(nyrs) # compare combined tax liability from full and sub samples for each year taxes_subsample = adt_subsample.loc["Combined Liability ($b)"] msg = '' for cyr in range(calc_start_year, calc_start_year + nyrs): reltol = 0.01 # maximum allowed relative difference in tax liability if not np.allclose(taxes_subsample[cyr], taxes_fullsample[cyr], atol=0.0, rtol=reltol): reldiff = (taxes_subsample[cyr] / taxes_fullsample[cyr]) - 1. line1 = '\nPUFCSV AGG SUB-vs-FULL RESULTS DIFFER IN {}' line2 = '\n when subfrac={:.3f}, rtol={:.4f}, seed={}' line3 = '\n with sub={:.3f}, full={:.3f}, rdiff={:.4f}' msg += line1.format(cyr) msg += line2.format(subfrac, reltol, rn_seed) msg += line3.format(taxes_subsample[cyr], taxes_fullsample[cyr], reldiff) if msg: raise ValueError(msg)