From 99335d38766469c2264e29e7d2d88a511fc9c962 Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Tue, 30 Apr 2019 22:58:27 -0400 Subject: [PATCH 1/6] Streamline read JSON logic --- taxcalc/calculator.py | 177 ++----------------------------- taxcalc/consumption.py | 10 ++ taxcalc/growdiff.py | 11 ++ taxcalc/parameters.py | 86 +++++++++++++++ taxcalc/policy.py | 10 ++ taxcalc/tests/test_calculator.py | 43 -------- taxcalc/tests/test_parameters.py | 42 ++++++++ taxcalc/tests/test_responses.py | 20 ++-- 8 files changed, 179 insertions(+), 220 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index 45fbac729..80722c25b 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -7,10 +7,7 @@ # # pylint: disable=invalid-name,no-value-for-parameter,too-many-lines -import os -import re import copy -import requests import numpy as np import pandas as pd from taxcalc.calcfunctions import (TaxInc, SchXYZTax, GainsTax, AGIsurtax, @@ -31,8 +28,7 @@ from taxcalc.consumption import Consumption from taxcalc.growdiff import GrowDiff from taxcalc.growfactors import GrowFactors -from taxcalc.utils import (json_to_dict, - DIST_VARIABLES, create_distribution_table, +from taxcalc.utils import (DIST_VARIABLES, create_distribution_table, DIFF_VARIABLES, create_difference_table, create_diagnostic_table, ce_aftertax_expanded_income, @@ -1060,7 +1056,7 @@ def decile_graph(self, calc, def read_json_param_objects(reform, assump): """ Read JSON reform and assump objects and - return a single dictionary containing four key:dict pairs: + return a composite dictionary containing four key:dict pairs: 'policy':dict, 'consumption':dict, 'growdiff_baseline':dict, and 'growdiff_response':dict. @@ -1069,15 +1065,14 @@ def read_json_param_objects(reform, assump): If assump is None, the dict in all the other key:dict pairs is empty. Also note that either of the two function arguments can be strings - containing a valid JSON string (rather than a local filename), - in which case the file reading is skipped and the appropriate - read_json_*_text method is called. + containing a valid JSON string (rather than a local filename). Either of the two function arguments can also be a valid URL string beginning with 'http' and pointing to a valid JSON file hosted online. The reform file/URL contents or JSON string must be like this: - {"policy": {...}} + {"policy": {...}} OR {...} + (in other words, the top-level policy key is optional) and the assump file/URL contents or JSON string must be like this: {"consumption": {...}, "growdiff_baseline": {...}, @@ -1098,59 +1093,12 @@ def read_json_param_objects(reform, assump): The 'growdiff_response' subdictionary of the returned dictionary is suitable as input into the GrowDiff.update_growdiff method. """ - # pylint: disable=too-many-branches - # first process the second assump argument - if assump is None: - cons_dict = dict() - gdiff_base_dict = dict() - gdiff_resp_dict = dict() - elif isinstance(assump, str): - if os.path.isfile(assump): - if not assump.endswith('.json'): - msg = "assump does not end with '.json': {}" - raise ValueError(msg.format(assump)) - txt = open(assump, 'r').read() - elif assump.startswith('http'): - if not assump.endswith('.json'): - msg = "assump does not end with '.json': {}" - raise ValueError(msg.format(assump)) - req = requests.get(assump) - req.raise_for_status() - txt = req.text - else: - txt = assump - (cons_dict, - gdiff_base_dict, - gdiff_resp_dict) = Calculator._read_json_econ_assump_text(txt) - else: - raise ValueError('assump is neither None nor string') - # next process the first reform argument - if reform is None: - rpol_dict = dict() - elif isinstance(reform, str): - if os.path.isfile(reform): - if not reform.endswith('.json'): - msg = "reform does not end with '.json': {}" - raise ValueError(msg.format(reform)) - txt = open(reform, 'r').read() - elif reform.startswith('http'): - if not reform.endswith('.json'): - msg = "reform does not end with '.json': {}" - raise ValueError(msg.format(reform)) - req = requests.get(reform) - req.raise_for_status() - txt = req.text - else: - txt = reform - rpol_dict = Calculator._read_json_policy_reform_text(txt) - else: - raise ValueError('reform is neither None nor string') - # construct single composite dictionary + # construct the composite dictionary param_dict = dict() - param_dict['policy'] = rpol_dict - param_dict['consumption'] = cons_dict - param_dict['growdiff_baseline'] = gdiff_base_dict - param_dict['growdiff_response'] = gdiff_resp_dict + param_dict['policy'] = Policy.read_json_reform(reform) + param_dict['consumption'] = Consumption.read_json_update(assump) + for topkey in ['growdiff_baseline', 'growdiff_response']: + param_dict[topkey] = GrowDiff.read_json_update(assump, topkey) # return the composite dictionary return param_dict @@ -1481,108 +1429,3 @@ def _calc_one_year(self, zero_out_calc_vars=False): C1040(self.__policy, self.__records) CTC_new(self.__policy, self.__records) IITAX(self.__policy, self.__records) - - @staticmethod - def _read_json_policy_reform_text(text_string): - """ - Strip //-comments from text_string and return 1 dict based on the JSON. - - Specified text is JSON with 1 high-level key:object pair: - a "policy": {...} pair. - - Other keys such as "consumption", "growdiff_baseline", or - "growdiff_response" will raise a ValueError. - - The {...} object may be empty (that is, be {}), or - may contain one or more pairs with parameter string primary keys and - string years as secondary keys. See test_json_reform_url() in the - tests/test_calculator.py for an extended example of a commented JSON - policy reform text that can be read by this method. - - Returned dictionaries pr_dict has string parameters as primary keys and - integer years as secondary keys (that is, they have a param:year:value - format). These returned dictionaries are suitable as the arguments to - the Policy.implement_reform(pr_dict) method. - """ - # strip out //-comments without changing line numbers - json_str = re.sub('//.*', ' ', text_string) - # convert JSON text into a Python dictionary - full_dict = json_to_dict(json_str) - # check key contents of dictionary - actual_keys = set(full_dict.keys()) - missing_keys = Calculator.REQUIRED_REFORM_KEYS - actual_keys - if missing_keys: - msg = 'required key(s) "{}" missing from policy reform file' - raise ValueError(msg.format(missing_keys)) - illegal_keys = actual_keys - Calculator.REQUIRED_REFORM_KEYS - if illegal_keys: - msg = 'illegal key(s) "{}" in policy reform file' - raise ValueError(msg.format(illegal_keys)) - # return the converted full_dict['policy'] dictionary - return Calculator._convert_year_to_int(full_dict['policy']) - - @staticmethod - def _read_json_econ_assump_text(text_string): - """ - Strip //-comments from text_string and return 3 dict based on the JSON. - - Specified text is JSON with 3 high-level key:value pairs: - a "consumption": {...} pair, - a "growdiff_baseline": {...} pair, and - a "growdiff_response": {...} pair. - - Other keys such as "policy" will raise a ValueError. - - The {...} object may be empty (that is, be {}), or - may contain one or more pairs with parameter string primary keys and - string years as secondary keys. See test_json_assump_url() in the - tests/test_calculator.py for an extended example of a commented JSON - economic assumption text that can be read by this method. - - Returned dictionaries (cons_dict, gdiff_baseline_dict, - gdiff_respose_dict) have string parameters as primary keys and - integer years as secondary keys (that is, they have a param:year:value - format). These returned dictionaries are suitable as the arguments to - the Consumption.update_consumption(cons_dict) method, or - the GrowDiff.update_growdiff(gdiff_dict) method. - """ - # strip out //-comments without changing line numbers - json_str = re.sub('//.*', ' ', text_string) - # convert JSON text into a Python dictionary - full_dict = json_to_dict(json_str) - # check key contents of dictionary - actual_keys = set(full_dict.keys()) - missing_keys = Calculator.REQUIRED_ASSUMP_KEYS - actual_keys - if missing_keys: - msg = 'required key(s) "{}" missing from economic assumption file' - raise ValueError(msg.format(missing_keys)) - illegal_keys = actual_keys - Calculator.REQUIRED_ASSUMP_KEYS - if illegal_keys: - msg = 'illegal key(s) "{}" in economic assumption file' - raise ValueError(msg.format(illegal_keys)) - # return the converted assumption dictionaries in full_dict as a tuple - return ( - Calculator._convert_year_to_int(full_dict['consumption']), - Calculator._convert_year_to_int(full_dict['growdiff_baseline']), - Calculator._convert_year_to_int(full_dict['growdiff_response']) - ) - - @staticmethod - def _convert_year_to_int(syr_dict): - """ - Converts specified syr_dict, which has string years as secondary - keys, into a dictionary with the same structure but having integer - years as secondary keys. - """ - iyr_dict = dict() - for pkey, sdict in syr_dict.items(): - assert isinstance(pkey, str) - assert pkey not in iyr_dict # will catch duplicate primary keys - iyr_dict[pkey] = dict() - assert isinstance(sdict, dict) - for skey, val in sdict.items(): - assert isinstance(skey, str) - year = int(skey) - assert year not in iyr_dict[pkey] # will catch duplicate years - iyr_dict[pkey][year] = val - return iyr_dict diff --git a/taxcalc/consumption.py b/taxcalc/consumption.py index 3a4969557..0be9ea904 100644 --- a/taxcalc/consumption.py +++ b/taxcalc/consumption.py @@ -37,6 +37,16 @@ def __init__(self): self.initialize(Consumption.JSON_START_YEAR, Consumption.DEFAULT_NUM_YEARS) + @staticmethod + def read_json_update(obj): + """ + Return a revision dictionary suitable for use with update_consumption + method derived from the specified JSON object, which can be None or + a string containing a local filename, a URL beginning with 'http' + pointing to a valid JSON file hosted online, or a valid JSON text. + """ + return Parameters._read_json_revision(obj, 'consumption') + def update_consumption(self, revision, print_warnings=True, raise_errors=True): """ diff --git a/taxcalc/growdiff.py b/taxcalc/growdiff.py index d7ac40562..17b5166fc 100644 --- a/taxcalc/growdiff.py +++ b/taxcalc/growdiff.py @@ -35,6 +35,17 @@ def __init__(self): self.initialize(GrowDiff.JSON_START_YEAR, GrowDiff.DEFAULT_NUM_YEARS) + @staticmethod + def read_json_update(obj, topkey): + """ + Return a revision dictionary suitable for use with update_growdiff + method generated from the specified JSON object, which can be None or + a string containing a local filename, a URL beginning with 'http' + pointing to a valid JSON file hosted online, or a valid JSON text. + """ + assert topkey in ('growdiff_baseline', 'growdiff_response') + return Parameters._read_json_revision(obj, topkey) + def update_growdiff(self, revision, print_warnings=True, raise_errors=True): """ diff --git a/taxcalc/parameters.py b/taxcalc/parameters.py index 4ee3bc29f..d2b865287 100644 --- a/taxcalc/parameters.py +++ b/taxcalc/parameters.py @@ -8,8 +8,10 @@ # pylint: disable=attribute-defined-outside-init,no-member import os +import re import abc from collections import OrderedDict +import requests import numpy as np from taxcalc.utils import read_egg_json, json_to_dict @@ -848,3 +850,87 @@ def _apply_cpi_offset_in_revision(self, revision): if name not in known_years: known_years[name] = kyrs_not_in_revision return known_years + + @staticmethod + def _read_json_revision(obj, topkey): + """ + Read JSON revision specified by obj and topkey + returning a single revision dictionary suitable for + use with the Parameters._update method. + + The obj function argument can be None or a string, where the + string contains a local filename, a URL beginning with 'http' + pointing to a valid JSON file hosted online, or valid JSON + text. + + The topkey argument must be a string containing the top-level + key in a compound-revision JSON text for which a revision + dictionary is returned. If the specified topkey is not among + the top-level JSON keys, the obj is assumed to be a + non-compound-revision JSON text for the specified topkey. + """ + # process the function arguments + if obj is None: + return dict() + if not isinstance(obj, str): + raise ValueError('obj is neither None nor a string') + if not isinstance(topkey, str): + raise ValueError('topkey={} is not a string'.format(topkey)) + valid_topkeys = [ + 'policy', + 'consumption', + 'growdiff_baseline', + 'growdiff_response' + ] + if topkey not in valid_topkeys: + msg = 'topkey={} is not a valid topkey' + raise ValueError(msg.format(topkey)) + if os.path.isfile(obj): + if not obj.endswith('.json'): + msg = 'obj does not end with ".json": {}' + raise ValueError(msg.format(obj)) + txt = open(obj, 'r').read() + elif obj.startswith('http'): + if not obj.endswith('.json'): + msg = 'obj does not end with ".json": {}' + raise ValueError(msg.format(obj)) + req = requests.get(obj) + req.raise_for_status() + txt = req.text + else: + txt = obj + # strip out //-comments without changing line numbers + json_txt = re.sub('//.*', ' ', txt) + # convert JSON text into a Python dictionary + full_dict = json_to_dict(json_txt) + # check top-level key contents of dictionary + if topkey in full_dict.keys(): + single_dict = full_dict[topkey] + else: + single_dict = full_dict + # convert string year to integer year in dictionary and return + return Parameters._convert_year_to_int(single_dict) + + @staticmethod + def _convert_year_to_int(syr_dict): + """ + Converts specified syr_dict, which has string years as secondary + keys, into a dictionary with the same structure but having integer + years as secondary keys. + """ + iyr_dict = dict() + for pkey, sdict in syr_dict.items(): + if not isinstance(pkey, str): + msg = 'primary_key={} is not a string' + raise ValueError(msg.format(pkey)) + iyr_dict[pkey] = dict() + if not isinstance(sdict, dict): + msg = 'primary_key={} has value that is not a dictionary' + raise ValueError(msg.format(pkey)) + for skey, val in sdict.items(): + if not isinstance(skey, str): + msg = 'secondary_key={} is not a string' + raise ValueError(msg.format(pkey)) + year = int(skey) + iyr_dict[pkey][year] = val + return iyr_dict diff --git a/taxcalc/policy.py b/taxcalc/policy.py index 9475f9872..2340a9b1a 100644 --- a/taxcalc/policy.py +++ b/taxcalc/policy.py @@ -99,6 +99,16 @@ def wage_growth_rates(self): """ return self._wage_growth_rates + @staticmethod + def read_json_reform(obj): + """ + Return a reform dictionary suitable for use with implement_reform + method generated from the specified JSON object, which can be None or + a string containing a local filename, a URL beginning with 'http' + pointing to a valid JSON file hosted online, or a valid JSON text. + """ + return Parameters._read_json_revision(obj, 'policy') + def implement_reform(self, reform, print_warnings=True, raise_errors=True): """ diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index e480852e6..d5f6e0a86 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -527,22 +527,10 @@ def test_read_bad_json_reform_file(): } } """ - 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): @@ -654,39 +642,8 @@ def test_read_bad_json_assump_file(): "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): diff --git a/taxcalc/tests/test_parameters.py b/taxcalc/tests/test_parameters.py index 75a7e5160..972cca1b0 100644 --- a/taxcalc/tests/test_parameters.py +++ b/taxcalc/tests/test_parameters.py @@ -467,3 +467,45 @@ def test_bool_int_value_info(tests_path, json_filename): pdict[param]['value_type'], valstr) assert msg == 'ERROR: boolean_value param has non-boolean value' + + +def test_read_json_revision(): + """ + Check _read_json_revision logic. + """ + good_revision = """{ + "consumption": {"BEN_mcaid_value": {"2013": 0.9}} + } + """ + bad_revision1 = """{ + 2019: {"param_name": 9.9} + } + """ + bad_revision2 = """{ + "unknown_topkey": {"param_name": 9.9} + } + """ + # pllint: disable=private-method + with pytest.raises(ValueError): + Parameters._read_json_revision(bad_revision1, '') + with pytest.raises(ValueError): + Parameters._read_json_revision(bad_revision2, '') + with pytest.raises(ValueError): + Parameters._read_json_revision(good_revision, 999) + + +def test_convert_year_to_int(): + """ + Check _convert_year_to_int logic. + """ + bad_dict1 = { # non-string primary key + 2019: {"param_name": 9.9} + } + bad_dict2 = { # non-string secondary key + "BEN_mcaid_value": {2013: 0.9} + } + # pllint: disable=private-method + with pytest.raises(ValueError): + Parameters._convert_year_to_int(bad_dict1) + with pytest.raises(ValueError): + Parameters._convert_year_to_int(bad_dict2) diff --git a/taxcalc/tests/test_responses.py b/taxcalc/tests/test_responses.py index 5d272e3b6..1232d133e 100644 --- a/taxcalc/tests/test_responses.py +++ b/taxcalc/tests/test_responses.py @@ -9,7 +9,7 @@ import glob import pytest # pylint: disable=unused-import # pylint: disable=import-error -from taxcalc import Calculator, Consumption, GrowDiff +from taxcalc import Consumption, GrowDiff def test_response_json(tests_path): @@ -28,15 +28,15 @@ def test_response_json(tests_path): '"growdiff_baseline"' in jpf_text and '"growdiff_response"' in jpf_text) if response_file: - # pylint: disable=protected-access - (con, gdiff_base, - gdiff_resp) = Calculator._read_json_econ_assump_text(jpf_text) - cons = Consumption() - cons.update_consumption(con) - growdiff_baseline = GrowDiff() - growdiff_baseline.update_growdiff(gdiff_base) - growdiff_response = GrowDiff() - growdiff_response.update_growdiff(gdiff_resp) + consumption = Consumption() + con_change = Consumption.read_json_update(jpf_text) + consumption.update_consumption(con_change) + del consumption + for topkey in['growdiff_baseline', 'growdiff_response']: + growdiff = GrowDiff() + gdiff_change = GrowDiff.read_json_update(jpf_text, topkey) + growdiff.update_growdiff(gdiff_change) + del growdiff else: # jpf_text is not a valid JSON response assumption file print('test-failing-filename: ' + jpf) From 7eb25cdad15f317eec92cf83de2a89cedc437a1c Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Wed, 1 May 2019 08:45:05 -0400 Subject: [PATCH 2/6] Simplify JSON reform files in taxcalc/reforms directory --- taxcalc/reforms/2017_law.json | 188 ++++++------ taxcalc/reforms/BrownKhanna.json | 12 +- taxcalc/reforms/Clinton2016.json | 28 +- taxcalc/reforms/Larson2019.json | 26 +- taxcalc/reforms/REFORMS.md | 18 +- taxcalc/reforms/Renacci.json | 155 ++++------ taxcalc/reforms/SandersDeFazio.json | 14 +- taxcalc/reforms/TCJA.json | 288 +++++++----------- taxcalc/reforms/Trump2016.json | 145 ++++----- taxcalc/reforms/Trump2017.json | 99 +++--- taxcalc/reforms/ptaxes0.json | 2 - taxcalc/reforms/ptaxes1.json | 2 - taxcalc/reforms/ptaxes2.json | 2 - taxcalc/reforms/ptaxes3.json | 2 - taxcalc/tests/test_parameters.py | 6 +- .../validation/taxsim/taxsim_emulation.json | 8 +- 16 files changed, 398 insertions(+), 597 deletions(-) diff --git a/taxcalc/reforms/2017_law.json b/taxcalc/reforms/2017_law.json index 24e0a293d..a9822904f 100644 --- a/taxcalc/reforms/2017_law.json +++ b/taxcalc/reforms/2017_law.json @@ -26,100 +26,98 @@ // NOTE: this reform projects pre-TCJA 2017 parameter values forward using the // unchained CPI-U price index. { - "policy": { - "CPI_offset": {"2017": 0.0025}, - "II_rt1": {"2018": 0.10}, - "II_brk1": {"2017": [9325, 18650, 9325, 13350, 18650]}, - "II_rt2": {"2018": 0.15}, - "II_brk2": {"2017": [37950, 75900, 37950, 50800, 75900]}, - "II_rt3": {"2018": 0.25}, - "II_brk3": {"2017": [91900, 153100, 76550, 131200, 153100]}, - "II_rt4": {"2018": 0.28}, - "II_brk4": {"2017": [191650, 233350, 116675, 212500, 233350]}, - "II_rt5": {"2018": 0.33}, - "II_brk5": {"2017": [416700, 416700, 208350, 416700, 416700]}, - "II_rt6": {"2018": 0.35}, - "II_brk6": {"2017": [418400, 470700, 235350, 444550, 470700]}, - "II_rt7": {"2018": 0.396}, - "PT_rt1": {"2018": 0.10}, - "PT_brk1": {"2017": [9325, 18650, 9325, 13350, 18650]}, - "PT_rt2": {"2018": 0.15}, - "PT_brk2": {"2017": [37950, 75900, 37950, 50800, 75900]}, - "PT_rt3": {"2018": 0.25}, - "PT_brk3": {"2017": [91900, 153100, 76550, 131200, 153100]}, - "PT_rt4": {"2018": 0.28}, - "PT_brk4": {"2017": [191650, 233350, 116675, 212500, 233350]}, - "PT_rt5": {"2018": 0.33}, - "PT_brk5": {"2017": [416700, 416700, 208350, 416700, 416700]}, - "PT_rt6": {"2018": 0.35}, - "PT_brk6": {"2017": [418400, 470700, 235350, 444550, 470700]}, - "PT_rt7": {"2018": 0.396}, - "PT_excl_rt": {"2018": 0}, - "PT_excl_wagelim_rt": {"2018": 9e99}, - "PT_excl_wagelim_thd": {"2018": [0, 0, 0, 0, 0]}, - "PT_excl_wagelim_prt": {"2018": [0, 0, 0, 0, 0]}, - "STD": {"2017": [6350, 12700, 6350, 9350, 12700]}, - "II_em": {"2017": 4050}, - "CTC_c": {"2018": 1000}, - "CTC_ps": {"2018": [75000, 110000, 55000, 75000, 75000]}, - "ACTC_c": {"2018": 1000}, - "ACTC_Income_thd": {"2018": 3000}, - "ODC_c": {"2018": 0}, - "AMT_em": {"2017": [54300, 84500, 42250, 54300, 84500]}, - "AMT_em_ps": {"2017": [120700, 160900, 80450, 120700, 160900]}, - "AMT_em_pe": {"2017": 249450}, - "ALD_BusinessLosses_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ALD_AlimonyPaid_hc": {"2019": 0}, - "ALD_AlimonyReceived_hc": {"2019": 1}, - "ALD_DomesticProduction_hc": {"2018": 0}, - "ID_prt": {"2018": 0.03}, - "ID_crt": {"2018": 0.80}, - "ID_Charity_crt_all": {"2018": 0.5}, - "ID_Casualty_hc": {"2018": 0}, - "ID_AllTaxes_c": {"2018": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_Miscellaneous_hc": {"2018": 0}, - "ID_Medical_frt": {"2017": 0.1}, + "CPI_offset": {"2017": 0.0025}, + "II_rt1": {"2018": 0.10}, + "II_brk1": {"2017": [9325, 18650, 9325, 13350, 18650]}, + "II_rt2": {"2018": 0.15}, + "II_brk2": {"2017": [37950, 75900, 37950, 50800, 75900]}, + "II_rt3": {"2018": 0.25}, + "II_brk3": {"2017": [91900, 153100, 76550, 131200, 153100]}, + "II_rt4": {"2018": 0.28}, + "II_brk4": {"2017": [191650, 233350, 116675, 212500, 233350]}, + "II_rt5": {"2018": 0.33}, + "II_brk5": {"2017": [416700, 416700, 208350, 416700, 416700]}, + "II_rt6": {"2018": 0.35}, + "II_brk6": {"2017": [418400, 470700, 235350, 444550, 470700]}, + "II_rt7": {"2018": 0.396}, + "PT_rt1": {"2018": 0.10}, + "PT_brk1": {"2017": [9325, 18650, 9325, 13350, 18650]}, + "PT_rt2": {"2018": 0.15}, + "PT_brk2": {"2017": [37950, 75900, 37950, 50800, 75900]}, + "PT_rt3": {"2018": 0.25}, + "PT_brk3": {"2017": [91900, 153100, 76550, 131200, 153100]}, + "PT_rt4": {"2018": 0.28}, + "PT_brk4": {"2017": [191650, 233350, 116675, 212500, 233350]}, + "PT_rt5": {"2018": 0.33}, + "PT_brk5": {"2017": [416700, 416700, 208350, 416700, 416700]}, + "PT_rt6": {"2018": 0.35}, + "PT_brk6": {"2017": [418400, 470700, 235350, 444550, 470700]}, + "PT_rt7": {"2018": 0.396}, + "PT_excl_rt": {"2018": 0}, + "PT_excl_wagelim_rt": {"2018": 9e99}, + "PT_excl_wagelim_thd": {"2018": [0, 0, 0, 0, 0]}, + "PT_excl_wagelim_prt": {"2018": [0, 0, 0, 0, 0]}, + "STD": {"2017": [6350, 12700, 6350, 9350, 12700]}, + "II_em": {"2017": 4050}, + "CTC_c": {"2018": 1000}, + "CTC_ps": {"2018": [75000, 110000, 55000, 75000, 75000]}, + "ACTC_c": {"2018": 1000}, + "ACTC_Income_thd": {"2018": 3000}, + "ODC_c": {"2018": 0}, + "AMT_em": {"2017": [54300, 84500, 42250, 54300, 84500]}, + "AMT_em_ps": {"2017": [120700, 160900, 80450, 120700, 160900]}, + "AMT_em_pe": {"2017": 249450}, + "ALD_BusinessLosses_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ALD_AlimonyPaid_hc": {"2019": 0}, + "ALD_AlimonyReceived_hc": {"2019": 1}, + "ALD_DomesticProduction_hc": {"2018": 0}, + "ID_prt": {"2018": 0.03}, + "ID_crt": {"2018": 0.80}, + "ID_Charity_crt_all": {"2018": 0.5}, + "ID_Casualty_hc": {"2018": 0}, + "ID_AllTaxes_c": {"2018": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_Miscellaneous_hc": {"2018": 0}, + "ID_Medical_frt": {"2017": 0.1}, - "ALD_Dependents_Child_c": {"2017": 0}, - "ALD_Dependents_Elder_c": {"2017": 0}, - "II_em_ps": {"2017": [261500, 313800, 156900, 287650, 313800]}, - "STD_Dep": {"2017": 1050}, - "STD_Aged": {"2017": [1550, 1250, 1250, 1550, 1550]}, - "II_credit": {"2017": [0, 0, 0, 0, 0]}, - "II_credit_ps": {"2017": [0, 0, 0, 0, 0]}, - "II_credit_nr": {"2017": [0, 0, 0, 0, 0]}, - "II_credit_nr_ps": {"2017": [0, 0, 0, 0, 0]}, - "ID_Medical_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_StateLocalTax_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_RealEstate_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_InterestPaid_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_Charity_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_Casualty_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_Miscellaneous_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_ps": {"2017": [261500, 313800, 156900, 287650, 313800]}, - "ID_BenefitSurtax_em": {"2017": [0, 0, 0, 0, 0]}, - "ID_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "CG_brk1": {"2017": [37950, 75900, 37950, 50800, 75900]}, - "CG_brk2": {"2017": [418400, 470700, 235350, 444550, 470700]}, - "CG_brk3": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "AMT_CG_brk1": {"2017": [37950, 75900, 37950, 50800, 75900]}, - "AMT_CG_brk2": {"2017": [418400, 470700, 235350, 444550, 470700]}, - "AMT_CG_brk3": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "CG_ec": {"2017": 0}, - "AMT_child_em": {"2017": 7500}, - "AMT_brk1": {"2017": 187800}, - "EITC_c": {"2017": [510, 3400, 5616, 6318]}, - "EITC_ps": {"2017": [8340, 18340, 18340, 18340]}, - "EITC_ps_MarriedJ": {"2017": [5600, 5600, 5600, 5600]}, - "EITC_InvestIncome_c": {"2017": 3450}, - "ETC_pe_Single": {"2017": 66}, - "ETC_pe_Married": {"2017": 132}, - "CTC_new_ps": {"2017": [0, 0, 0, 0, 0]}, - "FST_AGI_thd_lo": {"2017": [1.0e6, 1.0e6, 0.5e6, 1.0e6, 1.0e6]}, - "FST_AGI_thd_hi": {"2017": [2.0e6, 2.0e6, 1.0e6, 2.0e6, 2.0e6]}, - "AGI_surtax_thd": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "UBI_u18": {"2017": 0}, - "UBI_1820": {"2017": 0}, - "UBI_21": {"2017": 0} - } + "ALD_Dependents_Child_c": {"2017": 0}, + "ALD_Dependents_Elder_c": {"2017": 0}, + "II_em_ps": {"2017": [261500, 313800, 156900, 287650, 313800]}, + "STD_Dep": {"2017": 1050}, + "STD_Aged": {"2017": [1550, 1250, 1250, 1550, 1550]}, + "II_credit": {"2017": [0, 0, 0, 0, 0]}, + "II_credit_ps": {"2017": [0, 0, 0, 0, 0]}, + "II_credit_nr": {"2017": [0, 0, 0, 0, 0]}, + "II_credit_nr_ps": {"2017": [0, 0, 0, 0, 0]}, + "ID_Medical_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_StateLocalTax_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_RealEstate_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_InterestPaid_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_Charity_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_Casualty_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_Miscellaneous_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_ps": {"2017": [261500, 313800, 156900, 287650, 313800]}, + "ID_BenefitSurtax_em": {"2017": [0, 0, 0, 0, 0]}, + "ID_c": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "CG_brk1": {"2017": [37950, 75900, 37950, 50800, 75900]}, + "CG_brk2": {"2017": [418400, 470700, 235350, 444550, 470700]}, + "CG_brk3": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "AMT_CG_brk1": {"2017": [37950, 75900, 37950, 50800, 75900]}, + "AMT_CG_brk2": {"2017": [418400, 470700, 235350, 444550, 470700]}, + "AMT_CG_brk3": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "CG_ec": {"2017": 0}, + "AMT_child_em": {"2017": 7500}, + "AMT_brk1": {"2017": 187800}, + "EITC_c": {"2017": [510, 3400, 5616, 6318]}, + "EITC_ps": {"2017": [8340, 18340, 18340, 18340]}, + "EITC_ps_MarriedJ": {"2017": [5600, 5600, 5600, 5600]}, + "EITC_InvestIncome_c": {"2017": 3450}, + "ETC_pe_Single": {"2017": 66}, + "ETC_pe_Married": {"2017": 132}, + "CTC_new_ps": {"2017": [0, 0, 0, 0, 0]}, + "FST_AGI_thd_lo": {"2017": [1.0e6, 1.0e6, 0.5e6, 1.0e6, 1.0e6]}, + "FST_AGI_thd_hi": {"2017": [2.0e6, 2.0e6, 1.0e6, 2.0e6, 2.0e6]}, + "AGI_surtax_thd": {"2017": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "UBI_u18": {"2017": 0}, + "UBI_1820": {"2017": 0}, + "UBI_21": {"2017": 0} } diff --git a/taxcalc/reforms/BrownKhanna.json b/taxcalc/reforms/BrownKhanna.json index a29eb410c..56dd071e4 100644 --- a/taxcalc/reforms/BrownKhanna.json +++ b/taxcalc/reforms/BrownKhanna.json @@ -15,11 +15,9 @@ // - 4: EITC_ps // - 5: EITC_MinEligAge { - "policy": { - "EITC_c": {"2017": [3000, 6528, 10783, 12131]}, - "EITC_rt": {"2017": [0.3, 0.6258, 0.768, 0.864]}, - "EITC_prt": {"2017": [0.1598, 0.1598, 0.2106, 0.2106]}, - "EITC_ps": {"2017": [18340, 18340, 18340, 18340]}, - "EITC_MinEligAge": {"2017": 21} - } + "EITC_c": {"2017": [3000, 6528, 10783, 12131]}, + "EITC_rt": {"2017": [0.3, 0.6258, 0.768, 0.864]}, + "EITC_prt": {"2017": [0.1598, 0.1598, 0.2106, 0.2106]}, + "EITC_ps": {"2017": [18340, 18340, 18340, 18340]}, + "EITC_MinEligAge": {"2017": 21} } diff --git a/taxcalc/reforms/Clinton2016.json b/taxcalc/reforms/Clinton2016.json index 5450aab67..922b89af1 100644 --- a/taxcalc/reforms/Clinton2016.json +++ b/taxcalc/reforms/Clinton2016.json @@ -15,24 +15,16 @@ // - 4: CTC_c_under5_bonus, ACTC_rt_bonus_under5family // - 5: NIIT_PT_taxed { - "policy": { - "AGI_surtax_trt": - {"2017": 0.04}, - "AGI_surtax_thd": - {"2017": [5.0e6, 5.0e6, 2.5e6, 5.0e6, 5.0e6]}, - "FST_AGI_trt": - {"2017": 0.3}, - "ID_BenefitCap_Switch": - {"2017": [true, true, true, true, true, true, false]}, - "ID_BenefitCap_rt": - {"2017": 0.28}, - "CTC_c_under5_bonus": - {"2017": 1000}, - "ACTC_rt_bonus_under5family": - {"2017": 0.3}, - "NIIT_PT_taxed": - {"2017": true} - } + "AGI_surtax_trt": {"2017": 0.04}, + "AGI_surtax_thd": {"2017": [5.0e6, 5.0e6, 2.5e6, 5.0e6, 5.0e6]}, + "FST_AGI_trt": {"2017": 0.3}, + "ID_BenefitCap_Switch": { + "2017": [true, true, true, true, true, true, false] + }, + "ID_BenefitCap_rt": {"2017": 0.28}, + "CTC_c_under5_bonus": {"2017": 1000}, + "ACTC_rt_bonus_under5family": {"2017": 0.3}, + "NIIT_PT_taxed": {"2017": true} } // Note: Due to lack of detail, data, or modeling capability, many provisions cannot be scored. // These omitted provisions include: diff --git a/taxcalc/reforms/Larson2019.json b/taxcalc/reforms/Larson2019.json index 1873a1baf..7fb5f9520 100644 --- a/taxcalc/reforms/Larson2019.json +++ b/taxcalc/reforms/Larson2019.json @@ -12,18 +12,16 @@ // - 2: SS_Earnings_thd // - 3: FICA_ss_trt { - "policy": { - "SS_thd50": {"2019": [50000, 100000, 50000, 50000, 50000]}, - "SS_thd85": {"2019": [50000, 100000, 50000, 50000, 50000]}, - "SS_Earnings_thd": {"2019": 400000}, - "FICA_ss_trt": {"2020": 0.125, - "2021": 0.126, - "2022": 0.127, - "2023": 0.128, - "2024": 0.129, - "2025": 0.130, - "2026": 0.131, - "2027": 0.132, - "2028": 0.133} - } + "SS_thd50": {"2019": [50000, 100000, 50000, 50000, 50000]}, + "SS_thd85": {"2019": [50000, 100000, 50000, 50000, 50000]}, + "SS_Earnings_thd": {"2019": 400000}, + "FICA_ss_trt": {"2020": 0.125, + "2021": 0.126, + "2022": 0.127, + "2023": 0.128, + "2024": 0.129, + "2025": 0.130, + "2026": 0.131, + "2027": 0.132, + "2028": 0.133} } diff --git a/taxcalc/reforms/REFORMS.md b/taxcalc/reforms/REFORMS.md index 3fcb1b75b..35d2039aa 100644 --- a/taxcalc/reforms/REFORMS.md +++ b/taxcalc/reforms/REFORMS.md @@ -57,16 +57,14 @@ several reform provisions. The structure of this file is as follows: ``` { - "policy": { - : {: , - ..., - : }, - : {: }, - ..., - : {: , - ..., - : } - } + : {: , + ..., + : }, + : {: }, + ..., + : {: , + ..., + : } } ``` diff --git a/taxcalc/reforms/Renacci.json b/taxcalc/reforms/Renacci.json index b607a2a18..ff37b752e 100644 --- a/taxcalc/reforms/Renacci.json +++ b/taxcalc/reforms/Renacci.json @@ -3,110 +3,59 @@ // Reform_Reference: http://renacci.house.gov/_cache/files/7eeb81fe-f245-42c0-8de4-055ebf4c12e9/sats-white-paper.pdf // Reform_Baseline: 2017_law.json // Reform_Description: -// - Number of brackets reduced to 3 (1) -// - Standard deduction increased (2) -// - Personal exemption increased (3) -// - Eliminate all deductions except charitable and mortgage interest (4) -// - Cap mortgage interest deduction (4) -// - This limit was calculated by multiplying $500,000 by the current APR (%3.946 as of 5/24/2017) -// - Repeal Alternative Minimum Tax (5) -// - Capital gains and dividends are treated the same as ordinary income (6) -// - Increase in EITC (7) +// - Number of brackets reduced to 3 (1) +// - Standard deduction increased (2) +// - Personal exemption increased (3) +// - Eliminate all deductions except charitable and mortgage interest (4) +// - Cap mortgage interest deduction (4) +// This limit was calculated by multiplying $500,000 by +// the current APR (%3.946 as of 5/24/2017) +// - Repeal Alternative Minimum Tax (5) +// - Capital gains and dividends are treated the same as ordinary income (6) +// - Increase in EITC (7) // Reform_Parameter_Map: -// -1: II_rt*, II_brk* -// -2: STD -// -3: II_em -// -4: ID* -// -5: AMT_rt* -// -6: CG_nodiff -// -7: EITC_c +// 1: II_rt*, II_brk* +// 2: STD +// 3: II_em +// 4: ID* +// 5: AMT_rt* +// 6: CG_nodiff +// 7: EITC_c { - "policy":{ - "II_rt1": { - "2017": 0.1 - }, - "II_brk1": { - "2017": [50000,100000,50000,50000,50000] - }, - "II_rt2": { - "2017": 0.25 - }, - "II_brk2": { - "2017": [750000,1500000,750000,750000,750000] - }, - "II_rt3": { - "2017": 0.35 - }, - "II_brk3": { - "2017": [9e99,9e99,9e99,9e99,9e99] - }, - "II_rt4": { - "2017": 0.35 - }, - "II_brk4": { - "2017": [9e99,9e99,9e99,9e99,9e99] - }, - "II_rt5": { - "2017": 0.35 - }, - "II_brk5": { - "2017": [9e99,9e99,9e99,9e99,9e99] - }, - "II_rt6": { - "2017": 0.35 - }, - "II_brk6": { - "2017": [9e99,9e99,9e99,9e99,9e99] - }, - "II_rt7": { - "2017": 0.35 - }, - "II_brk7": { - "2017": [9e99,9e99,9e99,9e99,9e99] - }, - "STD": { - "2017": [15000,30000,15000,15000,15000] - }, - "II_em": { - "2017": 5000 - }, - "ID_StateLocalTax_hc": { - "2017": 1 - }, - "ID_Medical_hc": { - "2017": 1 - }, - "ID_Casualty_hc": { - "2017": 1 - }, - "ID_Miscellaneous_hc": { - "2017": 1 - }, - "ID_RealEstate_hc": { - "2017": 1 - }, - "ID_InterestPaid_c": { - "2017": [19730.0, 19730.0, 19730.0, 19730.0, 19730.0] - }, - "AMT_rt1": { - "2017": 0 - }, - "AMT_rt2": { - "2017": 0 - }, - "CG_nodiff": { - "2017": true - }, - "EITC_c": { - "2017": [1020, 4760, 7862, 8845] - } - } + "II_rt1": {"2017": 0.1}, + "II_brk1": {"2017": [50000,100000,50000,50000,50000]}, + "II_rt2": {"2017": 0.25}, + "II_brk2": {"2017": [750000,1500000,750000,750000,750000]}, + "II_rt3": {"2017": 0.35}, + "II_brk3": {"2017": [9e99,9e99,9e99,9e99,9e99]}, + "II_rt4": {"2017": 0.35}, + "II_brk4": {"2017": [9e99,9e99,9e99,9e99,9e99]}, + "II_rt5": {"2017": 0.35}, + "II_brk5": {"2017": [9e99,9e99,9e99,9e99,9e99]}, + "II_rt6": {"2017": 0.35}, + "II_brk6": {"2017": [9e99,9e99,9e99,9e99,9e99]}, + "II_rt7": {"2017": 0.35}, + "II_brk7": {"2017": [9e99,9e99,9e99,9e99,9e99]}, + "STD": {"2017": [15000,30000,15000,15000,15000]}, + "II_em": {"2017": 5000}, + "ID_StateLocalTax_hc": {"2017": 1}, + "ID_Medical_hc": {"2017": 1}, + "ID_Casualty_hc": {"2017": 1}, + "ID_Miscellaneous_hc": {"2017": 1}, + "ID_RealEstate_hc": {"2017": 1}, + "ID_InterestPaid_c": { + "2017": [19730.0, 19730.0, 19730.0, 19730.0, 19730.0] + }, + "AMT_rt1": {"2017": 0}, + "AMT_rt2": {"2017": 0}, + "CG_nodiff": {"2017": true}, + "EITC_c": {"2017": [1020, 4760, 7862, 8845]} } - -// Note: Due to lack of detail, data, or modeling capability, many provisions cannot be scored. +// Note: Due to lack of detail, data, or modeling capability, +// many provisions cannot be scored. // These omitted provisions include: -// - Repeal of the corporate income tax -// - 7% tax on business activities (credit-invoice value-added tax) -// - One time tax on accumulated foreign earnings abroad -// - This was included in the feedback section. -// - 9. Considers a border-adjustable-goods-and-services tax +// - Repeal of the corporate income tax +// - 7% tax on business activities (credit-invoice value-added tax) +// - One time tax on accumulated foreign earnings abroad +// - The following was included in the feedback section: +// - 9. Considers a border-adjustable-goods-and-services tax diff --git a/taxcalc/reforms/SandersDeFazio.json b/taxcalc/reforms/SandersDeFazio.json index aaddee825..1e8f9de56 100644 --- a/taxcalc/reforms/SandersDeFazio.json +++ b/taxcalc/reforms/SandersDeFazio.json @@ -4,14 +4,12 @@ // SSA Analysis: https://www.ssa.gov/OACT/solvency/SandersDeFazio_20190213.pdf // Reform_Baseline: policy_current_law.json // Reform_Description: -// - Apply the combined OASDI payroll tax rate on earnings above $250,000, effective 2020 (1) -// - Apply a separate 6.2 percent tax on investment income above ACA threshold (2) +// - Apply the combined OASDI payroll tax rate on earnings above $250,000 (1) +// - Apply a separate 6.2% tax on investment income above ACA threshold (2) // Reform_Parameter_Map: -// - 1: SS_Earnings_thd -// - 2: NIIT_rt +// 1: SS_Earnings_thd +// 2: NIIT_rt { - "policy": { - "SS_Earnings_thd": {"2020": 250000}, - "NIIT_rt": {"2020": 0.100} - } + "SS_Earnings_thd": {"2020": 250000}, + "NIIT_rt": {"2020": 0.100} } diff --git a/taxcalc/reforms/TCJA.json b/taxcalc/reforms/TCJA.json index 23a411439..dcda2ad21 100644 --- a/taxcalc/reforms/TCJA.json +++ b/taxcalc/reforms/TCJA.json @@ -4,15 +4,15 @@ // http://docs.house.gov/billsthisweek/20171218/CRPT-115HRPT-466.pdf // Reform_Baseline: 2017_law.json // Reform_Description: -// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) -// - New pass-through income tax schedule (2) -// - New standard deductions (3) -// - Repeal personal exemption (4) -// - Modification to child tax credit, nonrefundable dependent credits (5) -// - Modification of Alternative Minimum Tax exemption parameters (6) -// - Changes to certain above the line deductions (7) -// - Changes to itemized deductions (8) -// - Switch to chained CPI from CPI-U for tax parameter adjustment (9) +// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) +// - New pass-through income tax schedule (2) +// - New standard deductions (3) +// - Repeal personal exemption (4) +// - Modification to child tax credit, nonrefundable dependent credits (5) +// - Modification of Alternative Minimum Tax exemption parameters (6) +// - Changes to certain above the line deductions (7) +// - Changes to itemized deductions (8) +// - Switch to chained CPI from CPI-U for tax parameter adjustment (9) // Reform_Parameter_Map: // - 1: II_* // - 2: PT_* @@ -25,166 +25,112 @@ // - 9: CPI_offset // Note: II_brk*, PT_brk*, STD, II_em are rounded to nearest integer value { - "policy": { - "II_rt1": - {"2018": 0.10, - "2026": 0.10}, - "II_rt2": - {"2018": 0.12, - "2026": 0.15}, - "II_rt3": - {"2018": 0.22, - "2026": 0.25}, - "II_rt4": - {"2018": 0.24, - "2026": 0.28}, - "II_rt5": - {"2018": 0.32, - "2026": 0.33}, - "II_rt6": - {"2018": 0.35, - "2026": 0.35}, - "II_rt7": - {"2018": 0.37, - "2026": 0.396}, - "II_brk1": - {"2018": [9525, 19050, 9525, 13600, 19050], - "2026": [11242, 22484, 11242, 16094, 22484]}, - "II_brk2": - {"2018": [38700, 77400, 38700, 51800, 77400], - "2026": [45751, 91502, 45751, 61242, 91502]}, - "II_brk3": - {"2018": [82500, 165000, 82500, 82500, 165000], - "2026": [110791, 184571, 92286, 158169, 184571]}, - "II_brk4": - {"2018": [157500, 315000, 157500, 157500, 315000], - "2026": [231045, 281317, 140659, 256181, 281317]}, - "II_brk5": - {"2018": [200000, 400000, 200000, 200000, 400000], - "2026": [502356, 502356, 251178, 502356, 502356]}, - "II_brk6": - {"2018": [500000, 600000, 300000, 500000, 600000], - "2026": [504406 ,567457, 283728, 535931, 567457]}, - "PT_rt1": - {"2018": 0.10, - "2026": 0.10}, - "PT_rt2": - {"2018": 0.12, - "2026": 0.15}, - "PT_rt3": - {"2018": 0.22, - "2026": 0.25}, - "PT_rt4": - {"2018": 0.24, - "2026": 0.28}, - "PT_rt5": - {"2018": 0.32, - "2026": 0.33}, - "PT_rt6": - {"2018": 0.35, - "2026": 0.35}, - "PT_rt7": - {"2018": 0.37, - "2026": 0.396}, - "PT_brk1": - {"2018": [9525, 19050, 9525, 13600, 19050], - "2026": [11242, 22484, 11242, 16094, 22484]}, - "PT_brk2": - {"2018": [38700, 77400, 38700, 51800, 77400], - "2026": [45751, 91502, 45751, 61242, 91502]}, - "PT_brk3": - {"2018": [82500, 165000, 82500, 82500, 165000], - "2026": [110791, 184571, 92286, 158169, 184571]}, - "PT_brk4": - {"2018": [157500, 315000, 157500, 157500, 315000], - "2026": [231045, 281317, 140659, 256181, 281317]}, - "PT_brk5": - {"2018": [200000, 400000, 200000, 200000, 400000], - "2026": [502356, 502356, 251178, 502356, 502356]}, - "PT_brk6": - {"2018": [500000, 600000, 300000, 500000, 600000], - "2026": [504406, 567457, 283728, 535931, 567457]}, - "PT_excl_rt": - {"2018": 0.2, - "2026": 0.0}, - "PT_excl_wagelim_rt": - {"2018": 0.5, - "2026": 9e99}, - "PT_excl_wagelim_thd": - {"2018": [157500, 315000, 157500, 157500, 315000], - "2026": [0, 0, 0, 0, 0]}, - "PT_excl_wagelim_prt": - {"2018": [0.00002, 0.00001, 0.00002, 0.00002, 0.00001], - "2026": [0, 0, 0, 0, 0]}, - "STD": - {"2018": [12000, 24000, 12000, 18000, 24000], - "2026": [7655, 15311, 7655, 11272, 15311]}, - "II_em": - {"2018": 0, - "2026": 4883}, - "ODC_c": - {"2018": 500, - "2026": 0}, - "CTC_c": - {"2018": 2000, - "2026": 1000}, - "CTC_ps": - {"2018": [200000, 400000, 200000, 200000, 400000], - "2026": [75000, 110000, 55000, 75000, 75000]}, - "ACTC_c": - {"2018": 1400, - "2022": 1500, - "2025": 1600, - "2026": 1000}, - "ACTC_Income_thd": - {"2018": 2500, - "2026": 3000}, - "AMT_em": - {"2018": [70300, 109400, 54700, 70300, 109400], - "2026": [65462, 101870, 50935, 65461, 101870]}, - "AMT_em_ps": - {"2018": [500000, 1000000, 500000, 500000, 1000000], - "2026": [145511, 193974, 96987, 145511, 193974]}, - "AMT_em_pe": - {"2018": 718800, - "2026": 302083}, - "ALD_DomesticProduction_hc": - {"2018": 1, - "2026": 0}, - "ALD_AlimonyPaid_hc": - {"2019": 1, - "2026": 0}, - "ALD_AlimonyReceived_hc": - {"2019": 0, - "2026": 1}, - "ALD_BusinessLosses_c": - {"2018": [250000, 500000, 250000, 250000, 500000], - "2026": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_ps": - {"2018": [9e99, 9e99, 9e99, 9e99, 9e99], - "2026": [316675, 380010, 190005, 348343, 380010]}, - "ID_prt": - {"2018": 0.00, - "2026": 0.03}, - "ID_crt": - {"2018": 1.0, - "2026": 0.8}, - "ID_Charity_crt_all": - {"2018": 0.6, - "2026": 0.5}, - "ID_Casualty_hc": - {"2018": 1, - "2026": 0}, - "ID_AllTaxes_c": - {"2018": [10000, 10000, 5000, 10000, 10000], - "2026": [9e99, 9e99, 9e99, 9e99, 9e99]}, - "ID_Miscellaneous_hc": - {"2018": 1, - "2026": 0}, - "ID_Medical_frt": - {"2017": 0.075, - "2019": 0.100}, - "CPI_offset": - {"2017": -0.0025} - } + "II_rt1": {"2018": 0.10, + "2026": 0.10}, + "II_rt2": {"2018": 0.12, + "2026": 0.15}, + "II_rt3": {"2018": 0.22, + "2026": 0.25}, + "II_rt4": {"2018": 0.24, + "2026": 0.28}, + "II_rt5": {"2018": 0.32, + "2026": 0.33}, + "II_rt6": {"2018": 0.35, + "2026": 0.35}, + "II_rt7": {"2018": 0.37, + "2026": 0.396}, + "II_brk1": {"2018": [9525, 19050, 9525, 13600, 19050], + "2026": [11242, 22484, 11242, 16094, 22484]}, + "II_brk2": {"2018": [38700, 77400, 38700, 51800, 77400], + "2026": [45751, 91502, 45751, 61242, 91502]}, + "II_brk3": {"2018": [82500, 165000, 82500, 82500, 165000], + "2026": [110791, 184571, 92286, 158169, 184571]}, + "II_brk4": {"2018": [157500, 315000, 157500, 157500, 315000], + "2026": [231045, 281317, 140659, 256181, 281317]}, + "II_brk5": {"2018": [200000, 400000, 200000, 200000, 400000], + "2026": [502356, 502356, 251178, 502356, 502356]}, + "II_brk6": {"2018": [500000, 600000, 300000, 500000, 600000], + "2026": [504406 ,567457, 283728, 535931, 567457]}, + "PT_rt1": {"2018": 0.10, + "2026": 0.10}, + "PT_rt2": {"2018": 0.12, + "2026": 0.15}, + "PT_rt3": {"2018": 0.22, + "2026": 0.25}, + "PT_rt4": {"2018": 0.24, + "2026": 0.28}, + "PT_rt5": {"2018": 0.32, + "2026": 0.33}, + "PT_rt6": {"2018": 0.35, + "2026": 0.35}, + "PT_rt7": {"2018": 0.37, + "2026": 0.396}, + "PT_brk1": {"2018": [9525, 19050, 9525, 13600, 19050], + "2026": [11242, 22484, 11242, 16094, 22484]}, + "PT_brk2": {"2018": [38700, 77400, 38700, 51800, 77400], + "2026": [45751, 91502, 45751, 61242, 91502]}, + "PT_brk3": {"2018": [82500, 165000, 82500, 82500, 165000], + "2026": [110791, 184571, 92286, 158169, 184571]}, + "PT_brk4": {"2018": [157500, 315000, 157500, 157500, 315000], + "2026": [231045, 281317, 140659, 256181, 281317]}, + "PT_brk5": {"2018": [200000, 400000, 200000, 200000, 400000], + "2026": [502356, 502356, 251178, 502356, 502356]}, + "PT_brk6": {"2018": [500000, 600000, 300000, 500000, 600000], + "2026": [504406, 567457, 283728, 535931, 567457]}, + "PT_excl_rt": {"2018": 0.2, + "2026": 0.0}, + "PT_excl_wagelim_rt": {"2018": 0.5, + "2026": 9e99}, + "PT_excl_wagelim_thd": {"2018": [157500, 315000, 157500, 157500, 315000], + "2026": [0, 0, 0, 0, 0]}, + "PT_excl_wagelim_prt": { + "2018": [0.00002, 0.00001, 0.00002, 0.00002, 0.00001], + "2026": [0, 0, 0, 0, 0]}, + "STD": {"2018": [12000, 24000, 12000, 18000, 24000], + "2026": [7655, 15311, 7655, 11272, 15311]}, + "II_em": {"2018": 0, + "2026": 4883}, + "ODC_c": {"2018": 500, + "2026": 0}, + "CTC_c": {"2018": 2000, + "2026": 1000}, + "CTC_ps": {"2018": [200000, 400000, 200000, 200000, 400000], + "2026": [75000, 110000, 55000, 75000, 75000]}, + "ACTC_c": {"2018": 1400, + "2022": 1500, + "2025": 1600, + "2026": 1000}, + "ACTC_Income_thd": {"2018": 2500, + "2026": 3000}, + "AMT_em": {"2018": [70300, 109400, 54700, 70300, 109400], + "2026": [65462, 101870, 50935, 65461, 101870]}, + "AMT_em_ps": {"2018": [500000, 1000000, 500000, 500000, 1000000], + "2026": [145511, 193974, 96987, 145511, 193974]}, + "AMT_em_pe": {"2018": 718800, + "2026": 302083}, + "ALD_DomesticProduction_hc": {"2018": 1, + "2026": 0}, + "ALD_AlimonyPaid_hc": {"2019": 1, + "2026": 0}, + "ALD_AlimonyReceived_hc": {"2019": 0, + "2026": 1}, + "ALD_BusinessLosses_c": {"2018": [250000, 500000, 250000, 250000, 500000], + "2026": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_ps": {"2018": [9e99, 9e99, 9e99, 9e99, 9e99], + "2026": [316675, 380010, 190005, 348343, 380010]}, + "ID_prt": {"2018": 0.00, + "2026": 0.03}, + "ID_crt": {"2018": 1.0, + "2026": 0.8}, + "ID_Charity_crt_all": {"2018": 0.6, + "2026": 0.5}, + "ID_Casualty_hc": {"2018": 1, + "2026": 0}, + "ID_AllTaxes_c": {"2018": [10000, 10000, 5000, 10000, 10000], + "2026": [9e99, 9e99, 9e99, 9e99, 9e99]}, + "ID_Miscellaneous_hc": {"2018": 1, + "2026": 0}, + "ID_Medical_frt": {"2017": 0.075, + "2019": 0.100}, + "CPI_offset": {"2017": -0.0025} } diff --git a/taxcalc/reforms/Trump2016.json b/taxcalc/reforms/Trump2016.json index b5587e990..3ac723e61 100644 --- a/taxcalc/reforms/Trump2016.json +++ b/taxcalc/reforms/Trump2016.json @@ -3,15 +3,15 @@ // Reform_Reference: https://www.donaldjtrump.com/policies/tax-plan // Reform_Baseline: 2017_law.json // Reform_Description: -// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) -// - New pass-through income tax schedule (2) -// - New long-term capital gains and qualified dividends tax schedule (3) -// - Repeal Alternative Minimum Tax (4) -// - Repeal Net Investment Income Tax (5) -// - Raise the Standard Deduction (6) -// - Repeal the Personal Exemption (7) -// - New above the line deduction for child and elder care (8) -// - Cap itemized deductions (9) +// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) +// - New pass-through income tax schedule (2) +// - New long-term capital gains and qualified dividends tax schedule (3) +// - Repeal Alternative Minimum Tax (4) +// - Repeal Net Investment Income Tax (5) +// - Raise the Standard Deduction (6) +// - Repeal the Personal Exemption (7) +// - New above the line deduction for child and elder care (8) +// - Cap itemized deductions (9) // Reform_Parameter_Map: // - 1: II_rt*, II_brk* // - 2: PT_* @@ -23,88 +23,49 @@ // - 8: ALD_Dependents* // - 9: ID_c { - "policy": { - "II_rt1": - {"2017": 0.12}, - "II_brk1": - {"2017": [37500, 75000, 37500, 37500, 75000]}, - "II_rt2": - {"2017": 0.25}, - "II_brk2": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "II_rt3": - {"2017": 0.25}, - "II_brk3": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "II_rt4": - {"2017": 0.25}, - "II_brk4": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "II_rt5": - {"2017": 0.25}, - "II_brk5": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "II_rt6": - {"2017": 0.25}, - "II_brk6": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "II_rt7": - {"2017": 0.33}, - "PT_rt1": - {"2017": 0.12}, - "PT_brk1": - {"2017": [37500, 75000, 37500, 37500, 75000]}, - "PT_rt2": - {"2017": 0.15}, - "PT_brk2": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "PT_rt3": - {"2017": 0.15}, - "PT_brk3": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "PT_rt4": - {"2017": 0.15}, - "PT_brk4": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "PT_rt5": - {"2017": 0.15}, - "PT_brk5": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "PT_rt6": - {"2017": 0.15}, - "PT_brk6": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "PT_rt7": - {"2017": 0.15}, - "CG_brk1": - {"2017": [37500, 75000, 37500, 37500, 75000]}, - "CG_brk2": - {"2017": [112500, 225000, 112500, 112500, 225000]}, - "AMT_rt1": - {"2017": 0}, - "AMT_rt2": - {"2017": 0}, - "NIIT_rt": - {"2017": 0}, - "STD": - {"2017": [15000, 30000, 15000, 15000, 30000]}, - "II_em": - {"2017": 0}, - "ALD_Dependents_thd": - {"2017": [250000, 500000, 250000, 500000, 500000]}, - "ALD_Dependents_Elder_c": - {"2017": 5000}, - "ALD_Dependents_Child_c": - {"2017": 7156}, - "ID_c": - {"2017": [100000, 200000, 100000, 100000, 200000]} - } + "II_rt1": {"2017": 0.12}, + "II_brk1": {"2017": [37500, 75000, 37500, 37500, 75000]}, + "II_rt2": {"2017": 0.25}, + "II_brk2": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "II_rt3": {"2017": 0.25}, + "II_brk3": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "II_rt4": {"2017": 0.25}, + "II_brk4": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "II_rt5": {"2017": 0.25}, + "II_brk5": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "II_rt6": {"2017": 0.25}, + "II_brk6": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "II_rt7": {"2017": 0.33}, + "PT_rt1": {"2017": 0.12}, + "PT_brk1": {"2017": [37500, 75000, 37500, 37500, 75000]}, + "PT_rt2": {"2017": 0.15}, + "PT_brk2": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "PT_rt3": {"2017": 0.15}, + "PT_brk3": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "PT_rt4": {"2017": 0.15}, + "PT_brk4": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "PT_rt5": {"2017": 0.15}, + "PT_brk5": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "PT_rt6": {"2017": 0.15}, + "PT_brk6": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "PT_rt7": {"2017": 0.15}, + "CG_brk1": {"2017": [37500, 75000, 37500, 37500, 75000]}, + "CG_brk2": {"2017": [112500, 225000, 112500, 112500, 225000]}, + "AMT_rt1": {"2017": 0}, + "AMT_rt2": {"2017": 0}, + "NIIT_rt": {"2017": 0}, + "STD": {"2017": [15000, 30000, 15000, 15000, 30000]}, + "II_em": {"2017": 0}, + "ALD_Dependents_thd": {"2017": [250000, 500000, 250000, 500000, 500000]}, + "ALD_Dependents_Elder_c": {"2017": 5000}, + "ALD_Dependents_Child_c": {"2017": 7156}, + "ID_c": {"2017": [100000, 200000, 100000, 100000, 200000]} } - -// Note: Due to lack of detail, data, or modeling capability, many provisions cannot be scored. +// Note: Due to lack of detail, data, or modeling capability, +// many provisions cannot be scored. // These omitted provisions include: -// - Allow expenssing for pass-through firms -// - Tax carried interest as ordinary business income -// - Repeal pass-through business tax expenditures -// - Corporate tax provisions -// - Estate tax provisions +// - Allow expenssing for pass-through firms +// - Tax carried interest as ordinary business income +// - Repeal pass-through business tax expenditures +// - Corporate tax provisions +// - Estate tax provisions diff --git a/taxcalc/reforms/Trump2017.json b/taxcalc/reforms/Trump2017.json index 3c0c77e04..f4b1d2593 100644 --- a/taxcalc/reforms/Trump2017.json +++ b/taxcalc/reforms/Trump2017.json @@ -3,12 +3,12 @@ // Reform_Reference: 1-page hand-out from the White House briefing // Reform_Baseline: 2017_law.json // Reform_Description: -// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) -// - New pass-through income tax schedule (2) -// - Repeal Alternative Minimum Tax (3) -// - Repeal Net Investment Income Tax (4) -// - Double the Standard Deduction (5) -// - Eliminate all itemized deductions except mortgage interest and charitable contributions (6) +// - New personal income tax schedule (regular/non-AMT/non-pass-through) (1) +// - New pass-through income tax schedule (2) +// - Repeal Alternative Minimum Tax (3) +// - Repeal Net Investment Income Tax (4) +// - Double the Standard Deduction (5) +// - Eliminate all itemized deductions except mortgage interest and charitable contributions (6) // Reform_Parameter_Map: // - 1: II_rt* // - 2: PT_* @@ -17,60 +17,35 @@ // - 5: STD // - 6: ID_* { - "policy": { - "II_rt1": - {"2017": 0.10}, - "II_rt2": - {"2017": 0.10}, - "II_rt3": - {"2017": 0.25}, - "II_rt4": - {"2017": 0.25}, - "II_rt5": - {"2017": 0.25}, - "II_rt6": - {"2017": 0.35}, - "II_rt7": - {"2017": 0.35}, - "PT_rt1": - {"2017": 0.1}, - "PT_rt2": - {"2017": 0.1}, - "PT_rt3": - {"2017": 0.15}, - "PT_rt4": - {"2017": 0.15}, - "PT_rt5": - {"2017": 0.15}, - "PT_rt6": - {"2017": 0.15}, - "PT_rt7": - {"2017": 0.15}, - "AMT_rt1": - {"2017": 0}, - "AMT_rt2": - {"2017": 0}, - "NIIT_rt": - {"2017": 0}, - "STD": - {"2017": [12700, 25400, 12700, 18700, 25400]}, - "ID_StateLocalTax_hc": - {"2017": 1.0}, - "ID_Medical_hc": - {"2017": 1.0}, - "ID_Casualty_hc": - {"2017": 1.0}, - "ID_Miscellaneous_hc": - {"2017": 1.0}, - "ID_RealEstate_hc": - {"2017": 1.0} - } + "II_rt1": {"2017": 0.10}, + "II_rt2": {"2017": 0.10}, + "II_rt3": {"2017": 0.25}, + "II_rt4": {"2017": 0.25}, + "II_rt5": {"2017": 0.25}, + "II_rt6": {"2017": 0.35}, + "II_rt7": {"2017": 0.35}, + "PT_rt1": {"2017": 0.1}, + "PT_rt2": {"2017": 0.1}, + "PT_rt3": {"2017": 0.15}, + "PT_rt4": {"2017": 0.15}, + "PT_rt5": {"2017": 0.15}, + "PT_rt6": {"2017": 0.15}, + "PT_rt7": {"2017": 0.15}, + "AMT_rt1": {"2017": 0}, + "AMT_rt2": {"2017": 0}, + "NIIT_rt": {"2017": 0}, + "STD": {"2017": [12700, 25400, 12700, 18700, 25400]}, + "ID_StateLocalTax_hc": {"2017": 1.0}, + "ID_Medical_hc": {"2017": 1.0}, + "ID_Casualty_hc": {"2017": 1.0}, + "ID_Miscellaneous_hc": {"2017": 1.0}, + "ID_RealEstate_hc": {"2017": 1.0} } - -//Note: This reform file does not include several important reforms in the proposal. -// - Providing tax relief for families with child and dependent care expenses -// - 15% corporate income tax rate -// - Switch to a territorial tax system -// - One-time tax on repatriated profits held overseas -// - Eliminate tax breaks for special interests -// - Repeal of the estate tax +// Note: This reform file does not include several important provisions +// in the proposal: +// - Providing tax relief for families with child and dependent care expenses +// - 15% corporate income tax rate +// - Switch to a territorial tax system +// - One-time tax on repatriated profits held overseas +// - Eliminate tax breaks for special interests +// - Repeal of the estate tax diff --git a/taxcalc/reforms/ptaxes0.json b/taxcalc/reforms/ptaxes0.json index bc316139d..3bf989cb8 100644 --- a/taxcalc/reforms/ptaxes0.json +++ b/taxcalc/reforms/ptaxes0.json @@ -16,7 +16,6 @@ // 2021: 0.140 0.032 // 2022: 0.140 0.032 { - "policy": { "FICA_ss_trt": { "2018": 0.130, "2020": 0.140 @@ -25,5 +24,4 @@ "2019": 0.030, "2021": 0.032 } - } } diff --git a/taxcalc/reforms/ptaxes1.json b/taxcalc/reforms/ptaxes1.json index a8a3f72a3..bb35f846b 100644 --- a/taxcalc/reforms/ptaxes1.json +++ b/taxcalc/reforms/ptaxes1.json @@ -13,11 +13,9 @@ // 2020: 250000 // 2021: wage-indexed 250000 { - "policy": { "SS_Earnings_c": { "2018": 200000, "2020": 250000 } - } } diff --git a/taxcalc/reforms/ptaxes2.json b/taxcalc/reforms/ptaxes2.json index 4a14f6617..975fcab50 100644 --- a/taxcalc/reforms/ptaxes2.json +++ b/taxcalc/reforms/ptaxes2.json @@ -12,9 +12,7 @@ // 2020: 9e99 (9 with 99 zeros after it, a very large MTE) // 2021: wage-indexed 9e99 (an even bigger MTE value) { - "policy": { "SS_Earnings_c": { "2020": 9e99 } - } } diff --git a/taxcalc/reforms/ptaxes3.json b/taxcalc/reforms/ptaxes3.json index 773f27282..88214cd10 100644 --- a/taxcalc/reforms/ptaxes3.json +++ b/taxcalc/reforms/ptaxes3.json @@ -16,7 +16,6 @@ // 2019: 0.010 250000 300000 150000 250000 250000 // 2020: 0.010 ...... price indexed 2019 values ..... { - "policy": { "AMEDT_rt": { "2019": 0.010 }, @@ -26,5 +25,4 @@ "AMEDT_ec-indexed": { "2019": true } - } } diff --git a/taxcalc/tests/test_parameters.py b/taxcalc/tests/test_parameters.py index 972cca1b0..1c3639e3e 100644 --- a/taxcalc/tests/test_parameters.py +++ b/taxcalc/tests/test_parameters.py @@ -473,15 +473,15 @@ def test_read_json_revision(): """ Check _read_json_revision logic. """ - good_revision = """{ + good_revision = """{ "consumption": {"BEN_mcaid_value": {"2013": 0.9}} } """ - bad_revision1 = """{ + bad_revision1 = """{ 2019: {"param_name": 9.9} } """ - bad_revision2 = """{ + bad_revision2 = """{ "unknown_topkey": {"param_name": 9.9} } """ diff --git a/taxcalc/validation/taxsim/taxsim_emulation.json b/taxcalc/validation/taxsim/taxsim_emulation.json index 8da107cae..f6303d8f9 100644 --- a/taxcalc/validation/taxsim/taxsim_emulation.json +++ b/taxcalc/validation/taxsim/taxsim_emulation.json @@ -27,11 +27,7 @@ // income takes a filing unit above the ceiling. // { - "policy": { + "AMT_child_em_c_age": {"2013": 24}, - "AMT_child_em_c_age": {"2013": 24}, - - "EITC_excess_InvestIncome_rt": {"2013": 1.0} - - } + "EITC_excess_InvestIncome_rt": {"2013": 1.0} } From 7a17db6a04f8009bde43b48ba35ac59693bb9a44 Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Wed, 1 May 2019 10:04:55 -0400 Subject: [PATCH 3/6] Simplify tests using Policy.read_json_reform method --- taxcalc/tests/test_calculator.py | 147 ---------------------------- taxcalc/tests/test_policy.py | 115 ++++++++++++++++++++-- taxcalc/tests/test_puf_var_stats.py | 4 +- taxcalc/tests/test_reforms.py | 20 ++-- 4 files changed, 118 insertions(+), 168 deletions(-) diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index d5f6e0a86..cdd2a1399 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -383,116 +383,6 @@ def test_calculator_using_nonstd_input(): assert np.allclose(mtr_ptax, exp_mtr_ptax) -REFORM_JSON = """ -// Example of a reform file suitable for read_json_param_objects(). -// This JSON file can contain any number of trailing //-style comments, which -// will be removed before the contents are converted from JSON to a dictionary. -// Within each "policy" object, the primary keys are parameters and -// the secondary keys are years. -// Both the primary and secondary key values must be enclosed in quotes ("). -// Boolean variables are specified as true or false (no quotes; all lowercase). -{ - "policy": { - "AMT_brk1": // top of first AMT tax bracket - {"2015": 200000, - "2017": 300000 - }, - "EITC_c": // maximum 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 // values 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 // values in future years indexed with this year as base - } - } -} -""" - - -@pytest.mark.parametrize("set_year", [False, True]) -def test_read_json_reform_file_and_implement_reform(set_year): - """ - Test reading and translation of reform JSON 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_JSON, None) - 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_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_bad_json_names(tests_path): """ Test that ValueError raised with assump or reform do not end in '.json' @@ -508,37 +398,6 @@ def test_bad_json_names(tests_path): Calculator.read_json_param_objects(None, 'http://name.json.html') -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} - } - } - """ - 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(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_json_assump_url(): """ Test reading JSON assumption file using URL. @@ -656,8 +515,6 @@ def test_calc_all(): """ 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) @@ -665,7 +522,6 @@ def test_calc_all(): calc = Calculator(policy=pol, records=nonstd, sync_years=False) # keeps raw data unchanged assert calc.current_year == cyr - assert calc.reform_warnings == '' def test_noreform_documentation(): @@ -674,7 +530,6 @@ def test_noreform_documentation(): """ reform_json = """ { - "policy": {} } """ assump_json = """ @@ -705,7 +560,6 @@ def test_reform_documentation(): """ reform_json = """ { -"policy": { "II_em-indexed": { "2016": false, "2018": true @@ -729,7 +583,6 @@ def test_reform_documentation(): "2020": [false, false, false, false, false, false, false] } } -} """ assump_json = """ { diff --git a/taxcalc/tests/test_policy.py b/taxcalc/tests/test_policy.py index 106a3d635..808aebb7b 100644 --- a/taxcalc/tests/test_policy.py +++ b/taxcalc/tests/test_policy.py @@ -12,7 +12,7 @@ import numpy as np import pytest # pylint: disable=import-error -from taxcalc import Policy, Calculator +from taxcalc import Policy def test_incorrect_class_instantiation(): @@ -69,9 +69,112 @@ def test_policy_json_content_consistency(): assert vivals == expected_vi_vals[data['vi_name']] +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 + + +REFORM_JSON = """ +// Example of a reform file suitable for Policy.read_json_reform(). +// This JSON file 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 parameters and the secondary keys are years. +// Both the primary and secondary key values must be enclosed in quotes ("). +// Boolean variables are specified as true or false (no quotes; all lowercase). +{ + "AMT_brk1": // top of first AMT tax bracket + {"2015": 200000, + "2017": 300000 + }, + "EITC_c": // maximum 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 // values 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 // values in future years indexed with this year as base + } +} +""" + + # pylint: disable=protected-access,no-member +@pytest.mark.parametrize("set_year", [False, True]) +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,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_constant_inflation_rate_with_reform(): """ Test indexing of policy parameters involved in a reform. @@ -395,7 +498,7 @@ def test_reform_makes_no_changes_before_year(): @pytest.mark.parametrize("set_year", [False, True]) -def test_read_json_param_and_implement_reform(set_year): +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. @@ -403,7 +506,7 @@ def test_read_json_param_and_implement_reform(set_year): """ reform_json = """ // Example of JSON reform text suitable for the - // Calculator.read_json_param_objects() method. + // 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. @@ -412,7 +515,6 @@ def test_read_json_param_and_implement_reform(set_year): // 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 @@ -440,13 +542,12 @@ def test_read_json_param_and_implement_reform(set_year): "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']) + 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 diff --git a/taxcalc/tests/test_puf_var_stats.py b/taxcalc/tests/test_puf_var_stats.py index 9379eeedb..e989c5c09 100644 --- a/taxcalc/tests/test_puf_var_stats.py +++ b/taxcalc/tests/test_puf_var_stats.py @@ -151,9 +151,9 @@ def test_puf_var_stats(tests_path, puf_fullsample): """ # 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) + pre_tcja = Policy.read_json_reform(pre_tcja_jrf) baseline_policy = Policy() - baseline_policy.implement_reform(pre_tcja['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) diff --git a/taxcalc/tests/test_reforms.py b/taxcalc/tests/test_reforms.py index ebab1bcd2..47c8e320a 100644 --- a/taxcalc/tests/test_reforms.py +++ b/taxcalc/tests/test_reforms.py @@ -26,8 +26,7 @@ def test_2017_law_reform(tests_path): 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']) + 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 @@ -98,8 +97,7 @@ def test_round_trip_tcja_reform(tests_path): 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']) + 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 @@ -107,8 +105,7 @@ def test_round_trip_tcja_reform(tests_path): 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']) + 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 @@ -205,7 +202,7 @@ def res_and_out_are_same(base): 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) + 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) @@ -215,12 +212,12 @@ def res_and_out_are_same(base): 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) + reform = Policy.read_json_reform(jrf_text) pol = Policy() # current-law policy if pre_tcja_baseline: - pol.implement_reform(pre_tcja['policy']) + pol.implement_reform(pre_tcja) assert not pol.parameter_errors - pol.implement_reform(reform['policy']) + pol.implement_reform(reform) assert not pol.parameter_errors calc = Calculator(policy=pol, records=cases, verbose=False) calc.advance_to_year(tax_year) @@ -292,8 +289,7 @@ 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'] + return Policy.read_json_reform(pre_tcja_jrf) @pytest.fixture(scope='module', name='reforms_dict') From 53123b1e2df9f52fc3adde6d116f2340002ee768 Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Wed, 1 May 2019 10:56:23 -0400 Subject: [PATCH 4/6] Revise test_parameters.py to cover _read_json_revision code --- taxcalc/calculator.py | 7 ----- taxcalc/parameters.py | 46 ++++++++++++++------------------ taxcalc/tests/test_parameters.py | 35 +++++------------------- 3 files changed, 27 insertions(+), 61 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index 80722c25b..c9e032723 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -303,13 +303,6 @@ def consump_benval_params(self): """ return self.__consumption.benval_params() - @property - def reform_warnings(self): - """ - Calculator class embedded Policy object's reform_warnings. - """ - return self.__policy.parameter_warnings - @property def current_year(self): """ diff --git a/taxcalc/parameters.py b/taxcalc/parameters.py index d2b865287..1974419c5 100644 --- a/taxcalc/parameters.py +++ b/taxcalc/parameters.py @@ -869,7 +869,25 @@ def _read_json_revision(obj, topkey): the top-level JSON keys, the obj is assumed to be a non-compound-revision JSON text for the specified topkey. """ - # process the function arguments + # embedded function used only in _read_json_revision staticmethod + def convert_year_to_int(syr_dict): + """ + Converts specified syr_dict, which has string years as secondary + keys, into a dictionary with the same structure but having integer + years as secondary keys. + """ + iyr_dict = dict() + for pkey, sdict in syr_dict.items(): + assert isinstance(pkey, str) + iyr_dict[pkey] = dict() + assert isinstance(sdict, dict) + for skey, val in sdict.items(): + assert isinstance(skey, str) + year = int(skey) + iyr_dict[pkey][year] = val + return iyr_dict + # end of embedded function + # process the main function arguments if obj is None: return dict() if not isinstance(obj, str): @@ -909,28 +927,4 @@ def _read_json_revision(obj, topkey): else: single_dict = full_dict # convert string year to integer year in dictionary and return - return Parameters._convert_year_to_int(single_dict) - - @staticmethod - def _convert_year_to_int(syr_dict): - """ - Converts specified syr_dict, which has string years as secondary - keys, into a dictionary with the same structure but having integer - years as secondary keys. - """ - iyr_dict = dict() - for pkey, sdict in syr_dict.items(): - if not isinstance(pkey, str): - msg = 'primary_key={} is not a string' - raise ValueError(msg.format(pkey)) - iyr_dict[pkey] = dict() - if not isinstance(sdict, dict): - msg = 'primary_key={} has value that is not a dictionary' - raise ValueError(msg.format(pkey)) - for skey, val in sdict.items(): - if not isinstance(skey, str): - msg = 'secondary_key={} is not a string' - raise ValueError(msg.format(pkey)) - year = int(skey) - iyr_dict[pkey][year] = val - return iyr_dict + return convert_year_to_int(single_dict) diff --git a/taxcalc/tests/test_parameters.py b/taxcalc/tests/test_parameters.py index 1c3639e3e..a7727c305 100644 --- a/taxcalc/tests/test_parameters.py +++ b/taxcalc/tests/test_parameters.py @@ -473,39 +473,18 @@ def test_read_json_revision(): """ Check _read_json_revision logic. """ - good_revision = """{ + good_revision = """ + { "consumption": {"BEN_mcaid_value": {"2013": 0.9}} } """ - bad_revision1 = """{ - 2019: {"param_name": 9.9} - } - """ - bad_revision2 = """{ - "unknown_topkey": {"param_name": 9.9} - } - """ # pllint: disable=private-method with pytest.raises(ValueError): - Parameters._read_json_revision(bad_revision1, '') - with pytest.raises(ValueError): - Parameters._read_json_revision(bad_revision2, '') + # error because first obj argument is neither None nor a string + Parameters._read_json_revision(list(), '') with pytest.raises(ValueError): + # error because second topkey argument must be a string Parameters._read_json_revision(good_revision, 999) - - -def test_convert_year_to_int(): - """ - Check _convert_year_to_int logic. - """ - bad_dict1 = { # non-string primary key - 2019: {"param_name": 9.9} - } - bad_dict2 = { # non-string secondary key - "BEN_mcaid_value": {2013: 0.9} - } - # pllint: disable=private-method - with pytest.raises(ValueError): - Parameters._convert_year_to_int(bad_dict1) with pytest.raises(ValueError): - Parameters._convert_year_to_int(bad_dict2) + # error because second topkey argument a string but is not valid + Parameters._read_json_revision(good_revision, 'unknown_topkey') From 06b996783267bb0d8452d4256479a8061460ce0f Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Wed, 1 May 2019 13:01:07 -0400 Subject: [PATCH 5/6] Enhance Calculator.dataframe() method --- taxcalc/calculator.py | 29 ++++++++++++++++++++++------- taxcalc/tests/test_calculator.py | 3 ++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/taxcalc/calculator.py b/taxcalc/calculator.py index c9e032723..330260a1a 100644 --- a/taxcalc/calculator.py +++ b/taxcalc/calculator.py @@ -184,16 +184,24 @@ def total_weight(self): """ return self.array('s006').sum() - def dataframe(self, variable_list): + def dataframe(self, variable_list, all_vars=False): """ - Return Pandas DataFrame containing the listed variables from embedded - Records object. + Return Pandas DataFrame containing the listed variables from the + embedded Records object. If all_vars is True, then the variable_list + is ignored and all variables used as input to and calculated by the + Calculator.calc_all() method (which does not include marginal tax + rates) are included in the returned Pandas DataFrame. """ - assert isinstance(variable_list, list) - arys = [self.array(vname) for vname in variable_list] - dframe = pd.DataFrame(data=np.column_stack(arys), - columns=variable_list) + if all_vars: + varlist = list(self.__records.USABLE_READ_VARS | + self.__records.CALCULATED_VARS) + else: + assert isinstance(variable_list, list) + varlist = variable_list + arys = [self.array(varname) for varname in varlist] + dframe = pd.DataFrame(data=np.column_stack(arys), columns=varlist) del arys + del varlist return dframe def distribution_table_dataframe(self): @@ -303,6 +311,13 @@ def consump_benval_params(self): """ return self.__consumption.benval_params() + @property + def reform_warnings(self): + """ + Calculator class embedded Policy object's parameter_warnings. + """ + return self.__policy.parameter_warnings + @property def current_year(self): """ diff --git a/taxcalc/tests/test_calculator.py b/taxcalc/tests/test_calculator.py index cdd2a1399..99aed3bb0 100644 --- a/taxcalc/tests/test_calculator.py +++ b/taxcalc/tests/test_calculator.py @@ -69,6 +69,7 @@ def test_make_calculator_with_policy_reform(cps_subsample): pol.implement_reform(reform) # create a Calculator object using this policy reform calc = Calculator(policy=pol, records=rec) + assert calc.reform_warnings == '' # check that Policy object embedded in Calculator object is correct assert calc.current_year == year assert calc.policy_param('II_em') == 4000 @@ -280,7 +281,7 @@ def test_ID_HC_vs_BS(cps_subsample): bs_policy.implement_reform(bs_reform) bs_calc = Calculator(policy=bs_policy, records=recs) bs_calc.calc_all() - bs_taxes = bs_calc.dataframe(['iitax', 'payrolltax']) + bs_taxes = bs_calc.dataframe([], all_vars=True) del bs_calc # compare calculated taxes generated by the two reforms assert np.allclose(hc_taxes['payrolltax'], bs_taxes['payrolltax']) From 7653c8549a83705a46f63c2d5cebe6c4ee43060d Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Thu, 2 May 2019 08:02:17 -0400 Subject: [PATCH 6/6] Generalize Parameter.read_json_revision logic --- taxcalc/parameters.py | 9 --------- taxcalc/tests/test_parameters.py | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/taxcalc/parameters.py b/taxcalc/parameters.py index 1974419c5..f54c52879 100644 --- a/taxcalc/parameters.py +++ b/taxcalc/parameters.py @@ -894,15 +894,6 @@ def convert_year_to_int(syr_dict): raise ValueError('obj is neither None nor a string') if not isinstance(topkey, str): raise ValueError('topkey={} is not a string'.format(topkey)) - valid_topkeys = [ - 'policy', - 'consumption', - 'growdiff_baseline', - 'growdiff_response' - ] - if topkey not in valid_topkeys: - msg = 'topkey={} is not a valid topkey' - raise ValueError(msg.format(topkey)) if os.path.isfile(obj): if not obj.endswith('.json'): msg = 'obj does not end with ".json": {}' diff --git a/taxcalc/tests/test_parameters.py b/taxcalc/tests/test_parameters.py index a7727c305..dcc9cccf6 100644 --- a/taxcalc/tests/test_parameters.py +++ b/taxcalc/tests/test_parameters.py @@ -486,5 +486,5 @@ def test_read_json_revision(): # error because second topkey argument must be a string Parameters._read_json_revision(good_revision, 999) with pytest.raises(ValueError): - # error because second topkey argument a string but is not valid + # error because second topkey argument is not in good_revision Parameters._read_json_revision(good_revision, 'unknown_topkey')