Example #1
0
    def set_rates(self):
        """Initialize taxcalc indexing data."""
        cpi_vals = [
            vo["value"] for
            vo in self._data["parameter_indexing_CPI_offset"]["value"]
        ]
        # extend parameter_indexing_CPI_offset values through budget window
        # if they have not been extended already.
        cpi_vals = cpi_vals + cpi_vals[-1:] * (
            self.end_year - self.start_year + 1 - len(cpi_vals)
        )
        cpi_offset = {
            (self.start_year + ix): val
            for ix, val in enumerate(cpi_vals)
        }

        self._gfactors = GrowFactors()

        self._inflation_rates = [
            np.round(rate + cpi_offset[self.start_year + ix], 4)
            for ix, rate in enumerate(
                self._gfactors.price_inflation_rates(
                    self.start_year, self.end_year
                )
            )
        ]

        self._wage_growth_rates = self._gfactors.wage_growth_rates(
            self.start_year, self.end_year
        )
Example #2
0
    def __init__(self,
                 gfactors=None,
                 start_year=JSON_START_YEAR,
                 num_years=DEFAULT_NUM_YEARS):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')

        # read default parameters
        self._vals = self._params_dict_from_json_file()

        if start_year < Policy.JSON_START_YEAR:
            raise ValueError('start_year cannot be less than JSON_START_YEAR')
        if num_years < 1:
            raise ValueError('num_years cannot be less than one')

        syr = start_year
        lyr = start_year + num_years - 1
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], num_years)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)

        self.initialize(start_year, num_years)

        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._ignore_errors = False
 def cps_constructor(data=None,
                     gfactors=GrowFactors(),
                     exact_calculations=False):
     """
     Static method returns a Records object instantiated with CPS
     input data.  This works in a analogous way to Records(), which
     returns a Records object instantiated with PUF input data.
     This is a convenience method that eliminates the need to
     specify all the details of the CPS input data just as the
     default values of the arguments of the Records class constructor
     eliminate the need to specify all the details of the PUF input
     data.
     """
     if data is None:
         data = os.path.join(Records.CODE_PATH, 'cps.csv.gz')
     if gfactors is None:
         weights = None
     else:
         weights = os.path.join(Records.CODE_PATH,
                                Records.CPS_WEIGHTS_FILENAME)
     return Records(data=data,
                    start_year=Records.CPSCSV_YEAR,
                    gfactors=gfactors,
                    weights=weights,
                    adjust_ratios=Records.CPS_RATIOS_FILENAME,
                    exact_calculations=exact_calculations)
Example #4
0
 def __init__(self, gfactors=None, only_reading_defaults=False, **kwargs):
     # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary
     super().__init__()
     # handle gfactors argument
     if gfactors is None:
         self._gfactors = GrowFactors()
     elif isinstance(gfactors, GrowFactors):
         self._gfactors = gfactors
     else:
         raise ValueError('gfactors is not None or a GrowFactors instance')
     # read default parameters and initialize
     syr = Policy.JSON_START_YEAR
     lyr = Policy.LAST_BUDGET_YEAR
     nyrs = Policy.DEFAULT_NUM_YEARS
     self._inflation_rates = None
     self._wage_growth_rates = None
     self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR,
                     Policy.REMOVED_PARAMS, Policy.REDEFINED_PARAMS,
                     Policy.WAGE_INDEXED_PARAMS, **kwargs)
 def __init__(self,
              data=CIT_DATA_FILENAME,
              data_type='cross-section',
              gfactors=GrowFactors(),
              weights=CIT_WEIGHTS_FILENAME,
              panel_blowup=CIT_BLOWFACTORS_FILENAME,
              start_year=CITCSV_YEAR):
     # pylint: disable=too-many-arguments,too-many-locals
     self.__data_year = start_year
     # read specified data
     if data_type == 'cross-section':
         self.data_type = data_type
     elif data_type == 'panel':
         self.data_type = data_type
         self.blowfactors_path = panel_blowup
     else:
         raise ValueError('data_type is not cross-section or panel')
     self._read_data(data)
     # handle grow factors
     is_correct_type = isinstance(gfactors, GrowFactors)
     if gfactors is not None and not is_correct_type:
         msg = 'gfactors is neither None nor a GrowFactors instance'
         raise ValueError(msg)
     self.gfactors = gfactors
     # read sample weights
     self.WT = None
     self._read_weights(weights)
     # weights must be same size as tax record data
     if self.WT.size > 0 and self.array_length != len(self.WT.index):
         # scale-up sub-sample weights by year-specific factor
         sum_full_weights = self.WT.sum()
         self.WT = self.WT.iloc[self.__index[:len(self.WT.index)]]
         sum_sub_weights = self.WT.sum()
         factor = sum_full_weights / sum_sub_weights
         self.WT *= factor
     # specify current_year and ASSESSMENT_YEAR values
     if isinstance(start_year, int):
         self.__current_year = start_year
         self.ASSESSMENT_YEAR.fill(start_year)
     else:
         msg = 'start_year is not an integer'
         raise ValueError(msg)
     # construct sample weights for current_year
     if self.WT.size > 0:
         wt_colname = 'WT{}'.format(self.current_year)
         if wt_colname in self.WT.columns:
             if len(self.WT[wt_colname]) == self.array_length:
                 self.weight = self.WT[wt_colname]
             else:
                 self.weight = (np.ones(self.array_length) *
                                sum(self.WT[wt_colname]) /
                                len(self.WT[wt_colname]))
Example #6
0
    def __init__(self, gfactors=None):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')

        # read default parameters and initialize
        self._vals = self._params_dict_from_json_file()
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], nyrs)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
        self.initialize(syr, nyrs)

        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._ignore_errors = False
Example #7
0
 def __init__(self, gfactors=None, only_reading_defaults=False):
     # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary
     super().__init__()
     if only_reading_defaults:
         return
     # handle gfactors argument
     if gfactors is None:
         self._gfactors = GrowFactors()
     elif isinstance(gfactors, GrowFactors):
         self._gfactors = gfactors
     else:
         raise ValueError('gfactors is not None or a GrowFactors instance')
     # read default parameters and initialize
     syr = Policy.JSON_START_YEAR
     lyr = Policy.LAST_BUDGET_YEAR
     nyrs = Policy.DEFAULT_NUM_YEARS
     self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
     self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
     self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR,
                     Policy.REMOVED_PARAMS,
                     Policy.REDEFINED_PARAMS,
                     Policy.WAGE_INDEXED_PARAMS)
 def __init__(self,
              data=PIT_DATA_FILENAME,
              gfactors=GrowFactors(),
              weights=PIT_WEIGHTS_FILENAME,
              start_year=PITCSV_YEAR):
     # pylint: disable=too-many-arguments,too-many-locals
     self.__data_year = start_year
     # read specified data
     self._read_data(data)
     # handle grow factors
     is_correct_type = isinstance(gfactors, GrowFactors)
     if gfactors is not None and not is_correct_type:
         msg = 'gfactors is neither None nor a GrowFactors instance'
         raise ValueError(msg)
     self.gfactors = gfactors
     # read sample weights
     self.WT = None
     self._read_weights(weights)
     # weights must be same size as tax record data
     if self.WT.size > 0 and self.array_length != len(self.WT.index):
         # scale-up sub-sample weights by year-specific factor
         sum_full_weights = self.WT.sum()
         self.WT = self.WT.iloc[self.__index]
         sum_sub_weights = self.WT.sum()
         factor = sum_full_weights / sum_sub_weights
         self.WT *= factor
     # specify current_year and AYEAR values
     if isinstance(start_year, int):
         self.__current_year = start_year
         self.AYEAR.fill(start_year)
     else:
         msg = 'start_year is not an integer'
         raise ValueError(msg)
     # construct sample weights for current_year
     if self.WT.size > 0:
         wt_colname = 'WT{}'.format(self.current_year)
         if wt_colname in self.WT.columns:
             self.weight = self.WT[wt_colname]
Example #9
0
 def with_suffix(gdict, growdiff_baseline_dict, growdiff_response_dict):
     """
     Return param_base:year dictionary having only suffix parameters.
     """
     if bool(growdiff_baseline_dict) or bool(growdiff_response_dict):
         gdiff_baseline = GrowDiff()
         gdiff_baseline.update_growdiff(growdiff_baseline_dict)
         gdiff_response = GrowDiff()
         gdiff_response.update_growdiff(growdiff_response_dict)
         growfactors = GrowFactors()
         gdiff_baseline.apply_to(growfactors)
         gdiff_response.apply_to(growfactors)
     else:
         gdiff_baseline = None
         gdiff_response = None
         growfactors = None
     pol = Policy(gfactors=growfactors)
     pol.ignore_reform_errors()
     odict = dict()
     for param in gdict.keys():
         odict[param] = dict()
         for year in sorted(gdict[param].keys()):
             odict[param][year] = dict()
             for suffix in gdict[param][year].keys():
                 plist = getattr(pol, param).tolist()
                 dvals = plist[int(year) - Policy.JSON_START_YEAR]
                 odict[param][year] = [dvals]
                 idx = Policy.JSON_REFORM_SUFFIXES[suffix]
                 odict[param][year][0][idx] = gdict[param][year][suffix]
                 udict = {int(year): {param: odict[param][year]}}
                 pol.implement_reform(udict,
                                      print_warnings=False,
                                      raise_errors=False)
     del gdiff_baseline
     del gdiff_response
     del growfactors
     del pol
     return odict
Example #10
0
    def __init__(self, gfactors=None):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')

        # read default parameters and initialize
        self._vals = self._params_dict_from_json_file()
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], nyrs)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
        self.initialize(syr, nyrs)

        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._ignore_errors = False
Example #11
0
class Policy(ParametersBase):
    """
    Policy is a subclass of the abstract ParametersBase class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: GrowFactors class instance
        containing price inflation rates and wage growth rates

    start_year: integer
        first calendar year for historical policy parameters.

    num_years: integer
        number of calendar years for which to specify policy parameter
        values beginning with start_year.

    Raises
    ------
    ValueError:
        if gfactors is not a GrowFactors class instance.
        if start_year is less than JSON_START_YEAR.
        if num_years is less than one.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILENAME = 'current_law_policy.json'
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2017  # last year for which indexed param vals are known
    LAST_BUDGET_YEAR = 2027  # increases by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    def __init__(self,
                 gfactors=None,
                 start_year=JSON_START_YEAR,
                 num_years=DEFAULT_NUM_YEARS):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')

        # read default parameters
        self._vals = self._params_dict_from_json_file()

        if start_year < Policy.JSON_START_YEAR:
            raise ValueError('start_year cannot be less than JSON_START_YEAR')
        if num_years < 1:
            raise ValueError('num_years cannot be less than one')

        syr = start_year
        lyr = start_year + num_years - 1
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], num_years)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)

        self.initialize(start_year, num_years)

        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._ignore_errors = False

    def inflation_rates(self):
        """
        Returns list of price inflation rates starting with JSON_START_YEAR.
        """
        return self._inflation_rates

    def wage_growth_rates(self):
        """
        Returns list of wage growth rates starting with JSON_START_YEAR.
        """
        return self._wage_growth_rates

    def implement_reform(self, reform,
                         print_warnings=False, raise_errors=True):
        """
        Implement multi-year policy reform and leave current_year unchanged.

        Parameters
        ----------
        reform: dictionary of one or more YEAR:MODS pairs
            see Notes to Parameters _update method for info on MODS structure

        print_warnings: boolean
            if True, prints warnings when parameter_warnings exists;
            if False, does not print warnings when parameter_warnings exists
                    and leaves warning handling to caller of implement_reform.

        raise_errors: boolean
            if True, raises ValueError when parameter_errors exists;
            if False, does not raise ValueError when parameter_errors exists
                    and leaves error handling to caller of implement_reform.

        Raises
        ------
        ValueError:
            if reform is not a dictionary.
            if each YEAR in reform is not an integer.
            if minimum YEAR in the YEAR:MODS pairs is less than start_year.
            if minimum YEAR in the YEAR:MODS pairs is less than current_year.
            if maximum YEAR in the YEAR:MODS pairs is greater than end_year.
            if raise_errors is True AND
              _validate_parameter_names_types generates errors OR
              _validate_parameter_values generates errors.

        Returns
        -------
        nothing: void

        Notes
        -----
        Given a reform dictionary, typical usage of the Policy class
        is as follows::

            policy = Policy()
            policy.implement_reform(reform)

        In the above statements, the Policy() call instantiates a
        policy object (policy) containing current-law policy parameters,
        and the implement_reform(reform) call applies the (possibly
        multi-year) reform specified in reform and then sets the
        current_year to the value of current_year when implement_reform
        was called with parameters set for that pre-call year.

        An example of a multi-year, multi-parameter reform is as follows::

            reform = {
                2016: {
                    '_EITC_c': [[900, 5000, 8000, 9000]],
                    '_II_em': [7000],
                    '_SS_Earnings_c': [300000]
                },
                2017: {
                    '_SS_Earnings_c': [500000], '_SS_Earnings_c_cpi': False
                },
                2019: {
                    '_EITC_c': [[1200, 7000, 10000, 12000]],
                    '_II_em': [9000],
                    '_SS_Earnings_c': [700000], '_SS_Earnings_c_cpi': True
                }
            }

        Notice that each of the four YEAR:MODS pairs is specified as
        required by the private _update method, whose documentation
        provides several MODS dictionary examples.

        IMPORTANT NOTICE: when specifying a reform dictionary always group
        all reform provisions for a specified year into one YEAR:MODS pair.
        If you make the mistake of specifying two or more YEAR:MODS pairs
        with the same YEAR value, all but the last one will be overwritten,
        and therefore, not part of the reform.  This is because Python
        expects unique (not multiple) dictionary keys.  There is no way to
        catch this error, so be careful to specify reform dictionaries
        correctly.
        """
        # check that all reform dictionary keys are integers
        if not isinstance(reform, dict):
            raise ValueError('ERROR: YYYY PARAM reform is not a dictionary')
        if not reform:
            return  # no reform to implement
        reform_years = sorted(list(reform.keys()))
        for year in reform_years:
            if not isinstance(year, int):
                msg = 'ERROR: {} KEY {}'
                details = 'KEY in reform is not an integer calendar year'
                raise ValueError(msg.format(year, details))
        # check range of remaining reform_years
        first_reform_year = min(reform_years)
        if first_reform_year < self.start_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < start_year={}'
            raise ValueError(msg.format(first_reform_year, self.start_year))
        if first_reform_year < self.current_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < current_year={}'
            raise ValueError(msg.format(first_reform_year, self.current_year))
        last_reform_year = max(reform_years)
        if last_reform_year > self.end_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR > end_year={}'
            raise ValueError(msg.format(last_reform_year, self.end_year))
        # validate reform parameter names and types
        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._validate_parameter_names_types(reform)
        if not self._ignore_errors and self.parameter_errors:
            raise ValueError(self.parameter_errors)
        # optionally apply cpi_offset to inflation_rates and re-initialize
        if Policy._cpi_offset_in_reform(reform):
            known_years = self._apply_reform_cpi_offset(reform)
            self.set_default_vals(known_years=known_years)
        # implement the reform year by year
        precall_current_year = self.current_year
        reform_parameters = set()
        for year in reform_years:
            self.set_year(year)
            reform_parameters.update(reform[year].keys())
            self._update({year: reform[year]})
        self.set_year(precall_current_year)
        # validate reform parameter values
        self._validate_parameter_values(reform_parameters)
        if self.parameter_warnings and print_warnings:
            print(self.parameter_warnings)
        if self.parameter_errors and raise_errors:
            raise ValueError('\n' + self.parameter_errors)

    JSON_REFORM_SUFFIXES = {
        # MARS-indexed suffixes and list index numbers
        'single': 0,
        'joint': 1,
        'separate': 2,
        'headhousehold': 3,
        'widow': 4,
        # EIC-indexed suffixes and list index numbers
        '0kids': 0,
        '1kid': 1,
        '2kids': 2,
        '3+kids': 3,
        # idedtype-indexed suffixes and list index numbers
        'medical': 0,
        'statelocal': 1,
        'realestate': 2,
        'casualty': 3,
        'misc': 4,
        'interest': 5,
        'charity': 6
    }

    @staticmethod
    def translate_json_reform_suffixes(indict,
                                       growdiff_baseline_dict,
                                       growdiff_response_dict):
        """
        Replace any array parameters with suffixes in the specified
        JSON-derived "policy" dictionary, indict, and
        return a JSON-equivalent dictionary containing constructed array
        parameters and containing no parameters with suffixes, odict.
        """

        # define no_suffix function used only in this method
        def no_suffix(idict):
            """
            Return param_base:year dictionary having only no-suffix parameters.
            """
            odict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix not in suffixes:
                    odict[param] = idict[param]
            return odict

        # define group_dict function used only in this method
        def suffix_group_dict(idict):
            """
            Return param_base:year:suffix dictionary with each idict value.
            """
            gdict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix in suffixes:
                    del param_pieces[-1]
                    param_base = '_'.join(param_pieces)
                    if param_base not in gdict:
                        gdict[param_base] = dict()
                    for year in sorted(idict[param].keys()):
                        if year not in gdict[param_base]:
                            gdict[param_base][year] = dict()
                        gdict[param_base][year][suffix] = idict[param][year][0]
            return gdict

        # define with_suffix function used only in this method
        def with_suffix(gdict, growdiff_baseline_dict, growdiff_response_dict):
            """
            Return param_base:year dictionary having only suffix parameters.
            """
            if bool(growdiff_baseline_dict) or bool(growdiff_response_dict):
                gdiff_baseline = GrowDiff()
                gdiff_baseline.update_growdiff(growdiff_baseline_dict)
                gdiff_response = GrowDiff()
                gdiff_response.update_growdiff(growdiff_response_dict)
                growfactors = GrowFactors()
                gdiff_baseline.apply_to(growfactors)
                gdiff_response.apply_to(growfactors)
            else:
                gdiff_baseline = None
                gdiff_response = None
                growfactors = None
            pol = Policy(gfactors=growfactors)
            pol.ignore_reform_errors()
            odict = dict()
            for param in gdict.keys():
                odict[param] = dict()
                for year in sorted(gdict[param].keys()):
                    odict[param][year] = dict()
                    for suffix in gdict[param][year].keys():
                        plist = getattr(pol, param).tolist()
                        dvals = plist[int(year) - Policy.JSON_START_YEAR]
                        odict[param][year] = [dvals]
                        idx = Policy.JSON_REFORM_SUFFIXES[suffix]
                        odict[param][year][0][idx] = gdict[param][year][suffix]
                        udict = {int(year): {param: odict[param][year]}}
                        pol.implement_reform(udict,
                                             print_warnings=False,
                                             raise_errors=False)
            del gdiff_baseline
            del gdiff_response
            del growfactors
            del pol
            return odict

        # high-level logic of translate_json_reform_suffixes method:
        # - construct odict containing just parameters without a suffix
        odict = no_suffix(indict)
        # - group params with suffix into param_base:year:suffix dictionary
        gdict = suffix_group_dict(indict)
        # - add to odict the consolidated values for parameters with a suffix
        if gdict:
            odict.update(with_suffix(gdict,
                                     growdiff_baseline_dict,
                                     growdiff_response_dict))
        # - return policy dictionary containing constructed parameter arrays
        return odict

    def ignore_reform_errors(self):
        """
        Sets self._ignore_errors to True.
        """
        self._ignore_errors = True

    # ----- begin private methods of Policy class -----

    def _apply_clp_cpi_offset(self, cpi_offset_clp_data, num_years):
        """
        Call this method from Policy constructor
        after self._inflation_rates has been set and
        before base class initialize method is called.
        (num_years is number of years for which inflation rates are specified)
        """
        ovalues = cpi_offset_clp_data['value']
        if len(ovalues) < num_years:  # extrapolate last known value
            ovalues = ovalues + ovalues[-1:] * (num_years - len(ovalues))
        for idx in range(0, num_years):
            infrate = round(self._inflation_rates[idx] + ovalues[idx], 6)
            self._inflation_rates[idx] = infrate

    @staticmethod
    def _cpi_offset_in_reform(reform):
        """
        Return true if cpi_offset is in reform; otherwise return false.
        """
        for year in reform:
            for name in reform[year]:
                if name == '_cpi_offset':
                    return True
        return False

    def _apply_reform_cpi_offset(self, reform):
        """
        Call this method ONLY if _cpi_offset_in_reform returns True.
        Apply CPI offset to inflation rates and
        revert indexed parameter values in preparation for re-indexing.
        Also, return known_years which is
        (first cpi_offset year - start year + 1).
        """
        # extrapolate cpi_offset reform
        self.set_year(self.start_year)
        first_cpi_offset_year = 0
        for year in sorted(reform.keys()):
            self.set_year(year)
            if '_cpi_offset' in reform[year]:
                if first_cpi_offset_year == 0:
                    first_cpi_offset_year = year
                oreform = {'_cpi_offset': reform[year]['_cpi_offset']}
                self._update({year: oreform})
        self.set_year(self.start_year)
        assert first_cpi_offset_year > 0
        # adjust inflation rates
        cpi_offset = getattr(self, '_cpi_offset')
        for idx in range(0, self.num_years):
            infrate = round(self._inflation_rates[idx] + cpi_offset[idx], 6)
            self._inflation_rates[idx] = infrate
        # revert CPI-indexed parameter values to current_law_policy.json values
        for name in self._vals.keys():
            if self._vals[name]['cpi_inflated']:
                setattr(self, name, self._vals[name]['value'])
        # return known_years
        return first_cpi_offset_year - self.start_year + 1

    def _validate_parameter_names_types(self, reform):
        """
        Check validity of parameter names and parameter types used
        in the specified reform dictionary.
        """
        # pylint: disable=too-many-branches,too-many-nested-blocks
        # pylint: disable=too-many-locals
        param_names = set(self._vals.keys())
        for year in sorted(reform.keys()):
            for name in reform[year]:
                if name.endswith('_cpi'):
                    if isinstance(reform[year][name], bool):
                        pname = name[:-4]  # root parameter name
                        if pname not in param_names:
                            msg = '{} {} unknown parameter name'
                            self.parameter_errors += (
                                'ERROR: ' + msg.format(year, name) + '\n'
                            )
                        else:
                            # check if root parameter is cpi inflatable
                            if not self._vals[pname]['cpi_inflatable']:
                                msg = '{} {} parameter is not cpi inflatable'
                                self.parameter_errors += (
                                    'ERROR: ' + msg.format(year, pname) + '\n'
                                )
                    else:
                        msg = '{} {} parameter is not true or false'
                        self.parameter_errors += (
                            'ERROR: ' + msg.format(year, name) + '\n'
                        )
                else:  # if name does not end with '_cpi'
                    if name not in param_names:
                        msg = '{} {} unknown parameter name'
                        self.parameter_errors += (
                            'ERROR: ' + msg.format(year, name) + '\n'
                        )
                    else:
                        # check parameter value type avoiding use of isinstance
                        # because isinstance(True, (int,float)) is True, which
                        # makes it impossible to check float parameters
                        bool_param_type = self._vals[name]['boolean_value']
                        int_param_type = self._vals[name]['integer_value']
                        assert isinstance(reform[year][name], list)
                        pvalue = reform[year][name][0]
                        if isinstance(pvalue, list):
                            scalar = False  # parameter value is a list
                        else:
                            scalar = True  # parameter value is a scalar
                            pvalue = [pvalue]  # make scalar a single-item list
                        # pylint: disable=consider-using-enumerate
                        for idx in range(0, len(pvalue)):
                            if scalar:
                                pname = name
                            else:
                                pname = '{}_{}'.format(name, idx)
                            pval = pvalue[idx]
                            # pylint: disable=unidiomatic-typecheck
                            pval_is_bool = type(pval) == bool
                            pval_is_int = type(pval) == int
                            pval_is_float = type(pval) == float
                            if bool_param_type:
                                if not pval_is_bool:
                                    msg = '{} {} value {} is not boolean'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
                            elif int_param_type:
                                if not pval_is_int:
                                    msg = '{} {} value {} is not integer'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
                            else:  # param is float type
                                if not (pval_is_int or pval_is_float):
                                    msg = '{} {} value {} is not a number'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
        del param_names

    def _validate_parameter_values(self, parameters_set):
        """
        Check values of parameters in specified parameter_set using
        range information from the current_law_policy.json file.
        """
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-nested-blocks
        parameters = sorted(parameters_set)
        syr = Policy.JSON_START_YEAR
        for pname in parameters:
            if pname.endswith('_cpi'):
                continue  # *_cpi parameter values validated elsewhere
            pvalue = getattr(self, pname)
            for vop, vval in self._vals[pname]['range'].items():
                if isinstance(vval, str):
                    vvalue = getattr(self, vval)
                else:
                    vvalue = np.full(pvalue.shape, vval)
                assert pvalue.shape == vvalue.shape
                assert len(pvalue.shape) <= 2
                if len(pvalue.shape) == 2:
                    scalar = False  # parameter value is a list
                else:
                    scalar = True  # parameter value is a scalar
                for idx in np.ndindex(pvalue.shape):
                    out_of_range = False
                    if vop == 'min' and pvalue[idx] < vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} < min value {}'
                        extra = self._vals[pname]['out_of_range_minmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if vop == 'max' and pvalue[idx] > vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} > max value {}'
                        extra = self._vals[pname]['out_of_range_maxmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if out_of_range:
                        action = self._vals[pname]['out_of_range_action']
                        if scalar:
                            name = pname
                        else:
                            name = '{}_{}'.format(pname, idx[1])
                            if extra:
                                msg += '_{}'.format(idx[1])
                        if action == 'warn':
                            self.parameter_warnings += (
                                'WARNING: ' + msg.format(idx[0] + syr, name,
                                                         pvalue[idx],
                                                         vvalue[idx]) + '\n'
                            )
                        if action == 'stop':
                            self.parameter_errors += (
                                'ERROR: ' + msg.format(idx[0] + syr, name,
                                                       pvalue[idx],
                                                       vvalue[idx]) + '\n'
                            )
        del parameters
def generate_revenues():
    from taxcalc.growfactors import GrowFactors
    from taxcalc.policy import Policy
    from taxcalc.records import Records
    from taxcalc.gstrecords import GSTRecords
    from taxcalc.corprecords import CorpRecords
    from taxcalc.parameters import ParametersBase
    from taxcalc.calculator import Calculator
    """
    for num in range(1, num_reforms):
        block_selected_dict[num]['selected_item']= block_widget_dict[num][1].get()
        block_selected_dict[num]['selected_value']= block_widget_dict[num][3].get()
        block_selected_dict[num]['selected_year']= block_widget_dict[num][2].get()
    print(block_selected_dict)
    """
    f = open('reform.json')
    block_selected_dict = json.load(f)
    print("block_selected_dict from json", block_selected_dict)
    #print(block_selected_dict)
    # create Records object containing pit.csv and pit_weights.csv input data
    #print("growfactors filename ", growfactors_filename)
    #recs = Records(data=data_filename, weights=weights_filename, gfactors=GrowFactors(growfactors_filename=growfactors_filename))
    #recs = Records(data=data_filename, weights=weights_filename, gfactors=GrowFactors(growfactors_filename=growfactors_filename))

    #recs.increment_year1(3.0)

    #grecs = GSTRecords()
    f = open('global_vars.json')
    vars = json.load(f)

    print("data_filename: ", vars['cit_data_filename'])
    print("weights_filename: ", vars['cit_weights_filename'])
    print("growfactors_filename: ", vars['GROWFACTORS_FILENAME'])
    print("policy_filename: ", vars['DEFAULTS_FILENAME'])
    # create CorpRecords object using cross-section data
    #crecs1 = CorpRecords(data='cit_cross.csv', weights='cit_cross_wgts1.csv')
    crecs1 = CorpRecords(
        data=vars['cit_data_filename'],
        weights=vars['cit_weights_filename'],
        gfactors=GrowFactors(
            growfactors_filename=vars['GROWFACTORS_FILENAME']))
    #crecs1 = CorpRecords(data=vars['cit_weights_filename'], weights=vars['cit_weights_filename'])

    # Note: weights argument is optional
    assert isinstance(crecs1, CorpRecords)
    assert crecs1.current_year == 2017

    # create Policy object containing current-law policy
    pol = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])

    # specify Calculator objects for current-law policy
    #calc1 = Calculator(policy=pol, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)
    calc1 = Calculator(policy=pol, corprecords=crecs1, verbose=False)
    #calc1.increment_year1(3.8)
    assert isinstance(calc1, Calculator)
    assert calc1.current_year == 2017

    np.seterr(divide='ignore', invalid='ignore')

    calc1.calc_all()
    revenue_dict_cit = {}

    for year in range(2019, 2024):
        cols = []
        calc1.advance_to_year(year)

        # NOTE: calc1 now contains a PRIVATE COPY of pol and a PRIVATE COPY of recs,
        #       so we can continue to use pol and recs in this script without any
        #       concern about side effects from Calculator method calls on calc1.

        # Produce DataFrame of results using the calculator

        # First run the calculator for the corporate income tax
        calc1.calc_all()

        print("***** Year ", year)
        weighted_citax1 = calc1.weighted_total_cit('citax')
        citax_collection_billions1 = weighted_citax1 / 10**9
        citax_collection_str1 = '{0:.2f}'.format(citax_collection_billions1)

        print("The CIT Collection in billions is: ",
              citax_collection_billions1)

        # Store Results
        revenue_dict_cit[year] = {}
        revenue_dict_cit[year]['current_law'] = citax_collection_str1

    # start a new round of simulation for pit
    recs = Records(data=vars['pit_data_filename'],
                   weights=vars['pit_weights_filename'],
                   gfactors=GrowFactors(
                       growfactors_filename=vars['GROWFACTORS_FILENAME']))

    # create Policy object containing current-law policy
    pol = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])

    # specify Calculator objects for current-law policy
    #calc1 = Calculator(policy=pol, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)
    calc1 = Calculator(policy=pol, records=recs, verbose=False)
    #calc1.increment_year1(3.8)
    assert isinstance(calc1, Calculator)
    assert calc1.current_year == 2017

    np.seterr(divide='ignore', invalid='ignore')

    total_revenue_text = {}
    reform_revenue_text = {}
    revenue_dict_pit = {}
    revenue_amount_dict = {}
    num = 1
    first_time = True
    i = 1
    j = 0
    #rows = []

    window = tk.Toplevel()
    window.geometry("800x400+140+140")
    display_table(window, revenue_dict_cit, revenue_dict_pit, header=True)

    #for year in range(years[0], years[-1]+1):
    for year in range(2019, 2024):
        cols = []
        calc1.advance_to_year(year)

        # NOTE: calc1 now contains a PRIVATE COPY of pol and a PRIVATE COPY of recs,
        #       so we can continue to use pol and recs in this script without any
        #       concern about side effects from Calculator method calls on calc1.

        # Produce DataFrame of results using the calculator

        # First run the calculator for the corporate income tax
        calc1.calc_all()

        weighted_pitax1 = calc1.weighted_total_pit('pitax')
        pitax_collection_billions1 = weighted_pitax1 / 10**9
        pitax_collection_str1 = '{0:.2f}'.format(pitax_collection_billions1)

        print('\n\n\n')
        print(f'TAX COLLECTION FOR THE YEAR - {year} \n')
        print("The PIT Collection in billions is: ",
              pitax_collection_billions1)
        #total_revenue_text[year] = "PIT COLLECTION UNDER CURRENT LAW FOR THE YEAR - " + str(year)+" : "+str(pitax_collection_str1)+" bill"

        #save the results
        revenue_dict_pit[year] = {}
        revenue_dict_pit[year]['current_law'] = pitax_collection_str1

        display_table(window,
                      revenue_dict_cit,
                      revenue_dict_pit,
                      year=year,
                      row=i)
        i = i + 1

    display_table(window, revenue_dict_cit, revenue_dict_pit, footer=i)
    """
Example #13
0
 def __init__(self,
              data='puf.csv',
              exact_calculations=False,
              gfactors=GrowFactors(),
              weights=PUF_WEIGHTS_FILENAME,
              adjust_ratios=PUF_RATIOS_FILENAME,
              start_year=PUFCSV_YEAR):
     # pylint: disable=too-many-arguments,too-many-locals
     self.__data_year = start_year
     # read specified data
     self._read_data(data, exact_calculations)
     # check that three sets of split-earnings variables have valid values
     msg = 'expression "{0} == {0}p + {0}s" is not true for every record'
     tol = 0.020001  # handles "%.2f" rounding errors
     if not np.allclose(self.e00200, (self.e00200p + self.e00200s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00200'))
     if not np.allclose(self.e00900, (self.e00900p + self.e00900s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00900'))
     if not np.allclose(self.e02100, (self.e02100p + self.e02100s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e02100'))
     # check that ordinary dividends are no less than qualified dividends
     other_dividends = np.maximum(0., self.e00600 - self.e00650)
     if not np.allclose(self.e00600, self.e00650 + other_dividends,
                        rtol=0.0, atol=tol):
         msg = 'expression "e00600 >= e00650" is not true for every record'
         raise ValueError(msg)
     del other_dividends
     # check that total pension income is no less than taxable pension inc
     nontaxable_pensions = np.maximum(0., self.e01500 - self.e01700)
     if not np.allclose(self.e01500, self.e01700 + nontaxable_pensions,
                        rtol=0.0, atol=tol):
         msg = 'expression "e01500 >= e01700" is not true for every record'
         raise ValueError(msg)
     del nontaxable_pensions
     # handle grow factors
     is_correct_type = isinstance(gfactors, GrowFactors)
     if gfactors is not None and not is_correct_type:
         msg = 'gfactors is neither None nor a GrowFactors instance'
         raise ValueError(msg)
     self.gfactors = gfactors
     # read sample weights
     self.WT = None
     self._read_weights(weights)
     self.ADJ = None
     self._read_ratios(adjust_ratios)
     # weights must be same size as tax record data
     if self.WT.size > 0 and self.array_length != len(self.WT.index):
         # scale-up sub-sample weights by year-specific factor
         sum_full_weights = self.WT.sum()
         self.WT = self.WT.iloc[self.__index]
         sum_sub_weights = self.WT.sum()
         factor = sum_full_weights / sum_sub_weights
         self.WT *= factor
     # specify current_year and FLPDYR values
     if isinstance(start_year, int):
         self.__current_year = start_year
         self.FLPDYR.fill(start_year)
     else:
         msg = 'start_year is not an integer'
         raise ValueError(msg)
     # construct sample weights for current_year
     if self.WT.size > 0:
         wt_colname = 'WT{}'.format(self.current_year)
         if wt_colname in self.WT.columns:
             self.s006 = self.WT[wt_colname] * 0.01
     # specify that variable values do not include behavioral responses
     self.behavioral_responses_are_included = False
Example #14
0
    def init(self, input_data, tax_year, baseline, reform, assump,
             growdiff_growmodel, aging_input_data, exact_calculations):
        """
        TaxCalcIO class post-constructor method that completes initialization.

        Parameters
        ----------
        First five are same as the first five of the TaxCalcIO constructor:
            input_data, tax_year, baseline, reform, assump.

        growdiff_growmodel: GrowDiff object or None
            growdiff_growmodel GrowDiff object is used only in the
            TaxCalcIO.growmodel_analysis method.

        aging_input_data: boolean
            whether or not to extrapolate Records data from data year to
            tax_year.

        exact_calculations: boolean
            specifies whether or not exact tax calculations are done without
            any smoothing of "stair-step" provisions in the tax law.
        """
        # pylint: disable=too-many-arguments,too-many-locals
        # pylint: disable=too-many-statements,too-many-branches
        self.errmsg = ''
        # get policy parameter dictionary from --baseline file
        basedict = Calculator.read_json_param_objects(baseline, None)
        # get assumption sub-dictionaries
        paramdict = Calculator.read_json_param_objects(None, assump)
        # get policy parameter dictionaries from --reform file(s)
        policydicts = list()
        if self.specified_reform:
            reforms = reform.split('+')
            for ref in reforms:
                pdict = Calculator.read_json_param_objects(ref, None)
                policydicts.append(pdict['policy'])
            paramdict['policy'] = policydicts[0]
        # remember parameters for reform documentation
        self.param_dict = paramdict
        self.policy_dicts = policydicts
        # create Behavior object
        beh = Behavior()
        try:
            beh.update_behavior(paramdict['behavior'])
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        self.behavior_has_any_response = beh.has_any_response()
        # create gdiff_baseline object
        gdiff_baseline = GrowDiff()
        try:
            gdiff_baseline.update_growdiff(paramdict['growdiff_baseline'])
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        # create GrowFactors base object that incorporates gdiff_baseline
        gfactors_base = GrowFactors()
        gdiff_baseline.apply_to(gfactors_base)
        # specify gdiff_response object
        gdiff_response = GrowDiff()
        try:
            gdiff_response.update_growdiff(paramdict['growdiff_response'])
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        # create GrowFactors ref object that has all gdiff objects applied
        gfactors_ref = GrowFactors()
        gdiff_baseline.apply_to(gfactors_ref)
        gdiff_response.apply_to(gfactors_ref)
        if growdiff_growmodel:
            growdiff_growmodel.apply_to(gfactors_ref)
        # create Policy objects:
        # ... the baseline Policy object
        base = Policy(gfactors=gfactors_base)
        try:
            base.implement_reform(basedict['policy'],
                                  print_warnings=False,
                                  raise_errors=False)
            self.errmsg += base.parameter_errors
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        # ... the reform Policy object
        if self.specified_reform:
            pol = Policy(gfactors=gfactors_ref)
            for poldict in policydicts:
                try:
                    pol.implement_reform(poldict,
                                         print_warnings=False,
                                         raise_errors=False)
                    self.errmsg += pol.parameter_errors
                except ValueError as valerr_msg:
                    self.errmsg += valerr_msg.__str__()
        else:
            pol = Policy(gfactors=gfactors_base)
        # create Consumption object
        con = Consumption()
        try:
            con.update_consumption(paramdict['consumption'])
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        # create GrowModel object
        self.growmodel = GrowModel()
        try:
            self.growmodel.update_growmodel(paramdict['growmodel'])
        except ValueError as valerr_msg:
            self.errmsg += valerr_msg.__str__()
        # check for valid tax_year value
        if tax_year < pol.start_year:
            msg = 'tax_year {} less than policy.start_year {}'
            msg = msg.format(tax_year, pol.start_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        if tax_year > pol.end_year:
            msg = 'tax_year {} greater than policy.end_year {}'
            msg = msg.format(tax_year, pol.end_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        # any errors imply cannot proceed with calculations
        if self.errmsg:
            return
        # set policy to tax_year
        pol.set_year(tax_year)
        base.set_year(tax_year)
        # read input file contents into Records objects
        if aging_input_data:
            if self.cps_input_data:
                recs = Records.cps_constructor(
                    gfactors=gfactors_ref,
                    exact_calculations=exact_calculations)
                recs_base = Records.cps_constructor(
                    gfactors=gfactors_base,
                    exact_calculations=exact_calculations)
            else:  # if not cps_input_data but aging_input_data
                recs = Records(data=input_data,
                               gfactors=gfactors_ref,
                               exact_calculations=exact_calculations)
                recs_base = Records(data=input_data,
                                    gfactors=gfactors_base,
                                    exact_calculations=exact_calculations)
        else:  # input_data are raw data that are not being aged
            recs = Records(data=input_data,
                           gfactors=None,
                           exact_calculations=exact_calculations,
                           weights=None,
                           adjust_ratios=None,
                           start_year=tax_year)
            recs_base = copy.deepcopy(recs)
        if tax_year < recs.data_year:
            msg = 'tax_year {} less than records.data_year {}'
            msg = msg.format(tax_year, recs.data_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        # create Calculator objects
        self.calc = Calculator(policy=pol,
                               records=recs,
                               verbose=True,
                               consumption=con,
                               behavior=beh,
                               sync_years=aging_input_data)
        self.calc_base = Calculator(policy=base,
                                    records=recs_base,
                                    verbose=False,
                                    consumption=con,
                                    sync_years=aging_input_data)
Example #15
0
class Parameters(pt.Parameters):
    """
    Base Parameters class that wraps ParamTools, providing parameter indexing
    for tax policy in the adjust method and backwards-compatible preserving
    layer that supports Tax-Calculator's conventional reform formatting style
    as well as convenience methods like set_Year for classes operating on this
    one.

    The defaults file path may be set through the defaults class attribute
    variable or through the old DEFAULTS_FILE_NAME/DEFAULTS_FILE_PATH work
    flow.

    A custom getter method is implemented so that the value of a parameter
    over all allowed years can conveniently be retrieved by adding an
    underscore before the variable name (e.g. EITC_c vs _EITC_c).

    Note: Like all pt.Parameters classes the values of attributes
    corresponding to a parameter value on this class are ephemeral and the only
    way to make permanent changes to this class'sstate is through the set_state
    or adjust methods.

    """
    defaults = None
    array_first = True
    label_to_extend = "year"
    uses_extend_func = True

    REMOVED_PARAMS = None
    REDEFINED_PARAMS = None
    WAGE_INDEXED_PARAMS = ()

    # Legacy class attrs
    DEFAULTS_FILE_NAME = None
    DEFAULTS_FILE_PATH = None
    JSON_START_YEAR = None
    LAST_KNOWN_YEAR = None

    def __init__(self,
                 start_year=None,
                 num_years=None,
                 last_known_year=None,
                 removed=None,
                 redefined=None,
                 wage_indexed=None,
                 **kwargs):
        # In case we need to wait for this to be called from the
        # initialize method for legacy reasons.
        if not start_year or not num_years:
            return
        self._wage_growth_rates = None
        self._inflation_rates = None
        if (self.defaults is None and self.DEFAULTS_FILE_PATH is not None
                and self.DEFAULTS_FILE_NAME):
            self.defaults = os.path.join(self.DEFAULTS_FILE_PATH,
                                         self.DEFAULTS_FILE_NAME)

        if last_known_year is None:
            self._last_known_year = start_year
        else:
            assert last_known_year >= start_year
            assert last_known_year <= self.LAST_BUDGET_YEAR
            self._last_known_year = last_known_year

        self._removed_params = removed or self.REMOVED_PARAMS
        self._redefined_params = redefined or self.REDEFINED_PARAMS

        self._wage_indexed = wage_indexed or self.WAGE_INDEXED_PARAMS

        if ((start_year or self.JSON_START_YEAR)
                and "initial_state" not in kwargs):
            kwargs["initial_state"] = {
                "year": start_year or self.JSON_START_YEAR
            }
        super().__init__(**kwargs)
        self._init_values = {
            param: copy.deepcopy(data["value"])
            for param, data in self.read_params(self.defaults).items()
            if param != "schema"
        }

    def adjust(self, params_or_path, print_warnings=True, **kwargs):
        """
        Implements custom warning and error handling.

        If print_warnings is True, warnings are printed out and if
        print_warnings is False, nothing is printed.

        ParamTools throws an error if a warning is triggered and
        ignore_warnings is False. This method circumvents this behavior.
        """
        if print_warnings:
            _data = copy.deepcopy(self._data)
            kwargs["ignore_warnings"] = False
        else:
            kwargs["ignore_warnings"] = True
        self._warnings = {}
        try:
            return self.adjust_with_indexing(params_or_path, **kwargs)
        except pt.ValidationError as ve:
            if self.errors:
                raise ve
            if print_warnings:
                print("WARNING:")
                print(self.warnings)
            kwargs["ignore_warnings"] = True
            self._data = _data
            _warnings = copy.deepcopy(self._warnings)
            self._warnings = {}
            self._errors = {}
            adjustment = self.adjust_with_indexing(params_or_path, **kwargs)
            self._warnings = _warnings
            return adjustment

    def adjust_with_indexing(self, params_or_path, **kwargs):
        """
        Custom adjust method that handles special indexing logic. The logic
        is:

        1. If "parameter_indexing_CPI_offset" is adjusted, first set
        parameter_indexing_CPI_offset to zero before implementing the
        adjusted parameter_indexing_CPI_offset to avoid stacking adjustments.
        Then, revert all values of indexed parameters to the 'known' values:
            a. The current values of parameters that are being adjusted are
                deleted after the first year in which
                parameter_indexing_CPI_offset is adjusted.
            b. The current values of parameters that are not being adjusted
                (i.e. are not in params) are deleted after the last known year,
                with the exception of parameters that revert to their pre-TCJA
                values in 2026. Instead, these (2026) parameter values are
                recalculated using the new inflation rates.
            After the 'unknown' values have been deleted, the last known value
            is extrapolated through the budget window. If there are indexed
            parameters in the adjustment, they will be included in the final
            adjustment call (unless their indexed status is changed).
        2. If the "indexed" status is updated for any parameter:
            a. If a parameter has values that are being adjusted before
                the indexed status is adjusted, update those parameters first.
            b. Extend the values of that parameter to the year in which
                the status is changed.
            c. Change the indexed status for the parameter.
            d. Update parameter values in adjustment that are adjusted after
                the year in which the indexed status changes.
            e. Using the new "-indexed" status, extend the values of that
                parameter through the remaining years or until the -indexed
                status changes again.
        3. Update all parameters that are not indexing related, i.e. they are
            not "parameter_indexing_CPI_offset" or do not end with "-indexed".
        4. Return parsed adjustment with all adjustments, including "-indexed"
            parameters.

        Notable side-effects:
            - All values of a parameter whose indexed status is adjusted are
              wiped out after the year in which the value is adjusted for the
              same hard-coding reason.
        """
        # Temporarily turn off extra ops during the intermediary adjustments
        # so that expensive and unnecessary operations are not run.
        label_to_extend = self.label_to_extend
        array_first = self.array_first
        self.array_first = False
        self._gfactors = GrowFactors()

        params = self.read_params(params_or_path)

        # Check if parameter_indexing_CPI_offset is adjusted. If so, reset
        # values of all indexed parameters after year where
        # parameter_indexing_CPI_offset is changed. If
        # parameter_indexing_CPI_offset is changed multiple times, then
        # reset values after the first year in which the
        # parameter_indexing_CPI_offset is changed.
        needs_reset = []
        if params.get("parameter_indexing_CPI_offset") is not None:
            # Update parameter_indexing_CPI_offset with new value.
            cpi_adj = super().adjust(
                {
                    "parameter_indexing_CPI_offset":
                    params["parameter_indexing_CPI_offset"]
                }, **kwargs)
            # turn off extend now that parameter_indexing_CPI_offset
            # has been updated.
            self.label_to_extend = None
            # Get first year in which parameter_indexing_CPI_offset
            # is changed.
            cpi_min_year = min(cpi_adj["parameter_indexing_CPI_offset"],
                               key=lambda vo: vo["year"])

            rate_adjustment_vals = self.select_gte(
                "parameter_indexing_CPI_offset", year=cpi_min_year["year"])
            # "Undo" any existing parameter_indexing_CPI_offset for
            # years after parameter_indexing_CPI_offset has
            # been updated.
            self._inflation_rates = self._inflation_rates[:cpi_min_year[
                "year"] - self.start_year] + self._gfactors.price_inflation_rates(
                    cpi_min_year["year"], self.LAST_BUDGET_YEAR)

            # Then apply new parameter_indexing_CPI_offset values to
            # inflation rates
            for cpi_vo in rate_adjustment_vals:
                self._inflation_rates[cpi_vo["year"] -
                                      self.start_year] += cpi_vo["value"]
            # 1. Delete all unknown values.
            # 1.a For revision, these are years specified after cpi_min_year.
            init_vals = {}
            to_delete = {}
            for param in params:
                if (param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if param.endswith("-indexed"):
                    param = param.split("-indexed")[0]
                if self._data[param].get("indexed", False):
                    init_vals[param] = pt.select_lte(
                        self._init_values[param],
                        True,
                        {"year": cpi_min_year["year"]},
                    )
                    to_delete[param] = self.select_gt(
                        param, year=cpi_min_year["year"])
                    needs_reset.append(param)
            self.delete(to_delete, **kwargs)
            super().adjust(init_vals, **kwargs)

            # 1.b For all others, these are years after last_known_year.
            last_known_year = max(cpi_min_year["year"], self._last_known_year)
            # calculate 2026 value, using new inflation rates, for parameters
            # that revert to their pre-TCJA values.
            long_params = [
                'II_brk7', 'II_brk6', 'II_brk5', 'II_brk4', 'II_brk3',
                'II_brk2', 'II_brk1', 'PT_brk7', 'PT_brk6', 'PT_brk5',
                'PT_brk4', 'PT_brk3', 'PT_brk2', 'PT_brk1',
                'PT_qbid_taxinc_thd', 'ALD_BusinessLosses_c', 'STD', 'II_em',
                'II_em_ps', 'AMT_em', 'AMT_em_ps', 'AMT_em_pe', 'ID_ps',
                'ID_AllTaxes_c'
            ]
            final_ifactor = 1.0
            pyear = 2017  # prior year before TCJA first implemented
            fyear = 2026  # final year in which parameter values revert to
            # pre-TCJA values
            # construct final-year inflation factor from prior year
            # NOTE: pvalue[t+1] = pvalue[t] * ( 1 + irate[t] )
            for year in range(pyear, fyear):
                final_ifactor *= 1 + \
                    self._inflation_rates[year - self.start_year]

            long_param_vals = defaultdict(list)
            # compute final year parameter value
            for param in long_params:
                # only revert param in 2026 if it's not in revision
                if params.get(param) is None:
                    # grab param values from 2017
                    vos = self.select_eq(param, year=pyear)
                    # use final_ifactor to inflate from 2017 to 2026
                    for vo in vos:
                        long_param_vals[param].append(
                            # Create new dict to avoid modifying the original
                            dict(
                                vo,
                                value=min(
                                    9e99, round(vo["value"] * final_ifactor,
                                                0)),
                                year=fyear,
                            ))
                    needs_reset.append(param)
            super().adjust(long_param_vals, **kwargs)

            init_vals = {}
            to_delete = {}
            for param in self._data:
                if (param in params or param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if self._data[param].get("indexed", False):
                    init_vals[param] = pt.select_lte(self._init_values[param],
                                                     True,
                                                     {"year": last_known_year})
                    to_delete[param] = self.select_eq(param,
                                                      strict=True,
                                                      _auto=True)
                    needs_reset.append(param)

            self.delete(to_delete, **kwargs)
            super().adjust(init_vals, **kwargs)

            self.extend(label="year")

        # 2. Handle -indexed parameters.
        self.label_to_extend = None
        index_affected = set([])
        for param, values in params.items():
            if param.endswith("-indexed"):
                base_param = param.split("-indexed")[0]
                if not self._data[base_param].get("indexable", None):
                    msg = f"Parameter {base_param} is not indexable."
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                index_affected |= {param, base_param}
                indexed_changes = {}
                if isinstance(values, bool):
                    indexed_changes[self.start_year] = values
                elif isinstance(values, list):
                    for vo in values:
                        indexed_changes[vo.get("year",
                                               self.start_year)] = vo["value"]
                else:
                    msg = ("Index adjustment parameter must be a boolean or "
                           "list.")
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                # 2.a Adjust values less than first year in which index status
                # was changed.
                if base_param in params:
                    min_index_change_year = min(indexed_changes.keys())
                    vos = pt.select_lt(
                        params[base_param],
                        False,
                        {"year": min_index_change_year},
                    )
                    if vos:
                        min_adj_year = min(vos,
                                           key=lambda vo: vo["year"])["year"]
                        self.delete({
                            base_param:
                            self.select_gt(base_param, year=min_adj_year)
                        })
                        super().adjust({base_param: vos}, **kwargs)
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(
                                range(self.start_year, min_index_change_year)),
                        )

                for year in sorted(indexed_changes):
                    indexed_val = indexed_changes[year]
                    # Get and delete all default values after year where
                    # indexed status changed.
                    self.delete(
                        {base_param: self.select_gt(base_param, year=year)})

                    # 2.b Extend values for this parameter to the year where
                    # the indexed status changes.
                    if year > self.start_year:
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(range(self.start_year,
                                                    year + 1)),
                        )

                    # 2.c Set indexed status.
                    self._data[base_param]["indexed"] = indexed_val

                    # 2.d Adjust with values greater than or equal to current
                    # year in params
                    if base_param in params:
                        vos = pt.select_gte(params[base_param], False,
                                            {"year": year})
                        super().adjust({base_param: vos}, **kwargs)

                    # 2.e Extend values through remaining years.
                    self.extend(params=[base_param], label="year")

                needs_reset.append(base_param)
        # Re-instate ops.
        self.label_to_extend = label_to_extend
        self.array_first = array_first

        # Filter out "-indexed" params.
        nonindexed_params = {
            param: val
            for param, val in params.items() if param not in index_affected
        }

        needs_reset = set(needs_reset) - set(nonindexed_params.keys())
        if needs_reset:
            self._set_state(params=needs_reset)

        # 3. Do adjustment for all non-indexing related parameters.
        adj = super().adjust(nonindexed_params, **kwargs)

        # 4. Add indexing params back for return to user.
        adj.update({
            param: val
            for param, val in params.items() if param in index_affected
        })
        return adj

    def get_index_rate(self, param, label_to_extend_val):
        """
        Initalize indexing data and return the indexing rate value
        depending on the parameter name and label_to_extend_val, the value of
        label_to_extend.
        Returns: rate to use for indexing.
        """
        if not self._inflation_rates or not self._wage_growth_rates:
            self.set_rates()
        if param in self._wage_indexed:
            return self.wage_growth_rates(year=label_to_extend_val)
        else:
            return self.inflation_rates(year=label_to_extend_val)

    def set_rates(self):
        """
        This method is implemented by classes inheriting
        Parameters.
        """
        raise NotImplementedError()

    def wage_growth_rates(self, year=None):
        if year is not None:
            return self._wage_growth_rates[year - self.start_year]
        return self._wage_growth_rates or []

    def inflation_rates(self, year=None):
        if year is not None:
            return self._inflation_rates[year - self.start_year]
        return self._inflation_rates or []

    # alias methods below
    def initialize(self,
                   start_year,
                   num_years,
                   last_known_year=None,
                   removed=None,
                   redefined=None,
                   wage_indexed=None,
                   **kwargs):
        """
        Legacy method for initializing a Parameters instance. Projects
        should use the __init__ method in the future.
        """
        # case where project hasn't been initialized yet.
        if getattr(self, "_data", None) is None:
            return Parameters.__init__(self,
                                       start_year,
                                       num_years,
                                       last_known_year=last_known_year,
                                       removed=removed,
                                       redefined=redefined,
                                       wage_indexed=wage_indexed,
                                       **kwargs)

    def _update(self, revision, print_warnings, raise_errors):
        """
        A translation layer on top of Parameters.adjust. Projects
        that have historically used the `_update` method with
        Tax-Calculator styled adjustments can continue to do so
        without making any changes to how they handle adjustments.

        Converts reforms that are compatible with Tax-Calculator:

        adjustment = {
            "standard_deduction": {2024: [10000.0, 10000.0]},
            "ss_rate": {2024: 0.2}
        }

        into reforms that are compatible with ParamTools:

        {
            'standard_deduction': [
                {'year': 2024, 'marital_status': 'single', 'value': 10000.0},
                {'year': 2024, 'marital_status': 'joint', 'value': 10000.0}
            ],
            'ss_rate': [{'year': 2024, 'value': 0.2}]}
        }

        """
        if not isinstance(revision, dict):
            raise pt.ValidationError(
                {"errors": {
                    "schema": "Revision must be a dictionary."
                }}, None)
        new_params = defaultdict(list)
        for param, val in revision.items():
            if not isinstance(param, str):
                msg = f"Parameter {param} is not a string."
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
            if (param not in self._data
                    and param.split("-indexed")[0] not in self._data):
                if self._removed_params and param in self._removed_params:
                    msg = f"{param} {self._removed_params[param]}"
                elif (self._redefined_params
                      and param in self._redefined_params):
                    msg = self._redefined_params[param]
                else:
                    msg = f"Parameter {param} does not exist."
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
            if param.endswith("-indexed"):
                for year, yearval in val.items():
                    new_params[param] += [{"year": year, "value": yearval}]
            elif isinstance(val, dict):
                for year, yearval in val.items():
                    val = getattr(self, param)
                    if (self._data[param].get("type", None) == "str"
                            and isinstance(yearval, str)):
                        new_params[param] += [{"value": yearval}]
                        continue

                    yearval = np.array(yearval)
                    if (getattr(val, "shape", None)
                            and yearval.shape != val[0].shape):
                        exp_dims = val[0].shape
                        if exp_dims == tuple():
                            msg = (f"{param} is not an array " f"parameter.")
                        elif yearval.shape:
                            msg = (f"{param} has {yearval.shape[0]} elements "
                                   f"but should only have {exp_dims[0]} "
                                   f"elements.")
                        else:
                            msg = (f"{param} is an array parameter with "
                                   f"{exp_dims[0]} elements.")
                        raise pt.ValidationError({"errors": {
                            "schema": msg
                        }}, None)

                    value_objects = self.from_array(param,
                                                    yearval.reshape(
                                                        (1, *yearval.shape)),
                                                    year=year)
                    new_params[param] += value_objects
            else:
                msg = (f"{param} must be a year:value dictionary "
                       f"if you are not using the new adjust method.")
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
        return self.adjust(new_params,
                           print_warnings=print_warnings,
                           raise_errors=raise_errors)

    def set_year(self, year):
        self.set_state(year=year)

    @property
    def current_year(self):
        return self.label_grid["year"][0]

    @property
    def start_year(self):
        return self._stateless_label_grid["year"][0]

    @property
    def end_year(self):
        return self._stateless_label_grid["year"][-1]

    @property
    def num_years(self):
        return self.end_year - self.start_year + 1

    @property
    def parameter_warnings(self):
        return self.errors or ""

    @property
    def parameter_errors(self):
        return self.errors or ""

    @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.
        """

        # 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):
            raise ValueError('obj is neither None nor a string')
        if not isinstance(topkey, str):
            raise ValueError('topkey={} is not a string'.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 convert_year_to_int(single_dict)

    def metadata(self):
        return self.specification(meta_data=True, use_state=False)

    @staticmethod
    def years_in_revision(revision):
        """
        Return list of years in specified revision dictionary, which is
        assumed to have a param:year:value format.
        """
        assert isinstance(revision, dict)
        years = list()
        for _, paramdata in revision.items():
            assert isinstance(paramdata, dict)
            for year, _ in paramdata.items():
                assert isinstance(year, int)
                if year not in years:
                    years.append(year)
        return years

    def __getattr__(self, attr):
        """
        Allows the user to get the value of a parameter over all years,
        not just the ones that are active.
        """
        if (attr.startswith("_")
                and attr[1:] in super().__getattribute__("_data")):
            return self.to_array(attr[1:],
                                 year=list(
                                     range(self.start_year,
                                           self.end_year + 1)))
        else:
            raise AttributeError(f"{attr} not definied.")
def generate_policy_revenues():
    from taxcalc.growfactors import GrowFactors
    from taxcalc.policy import Policy
    from taxcalc.records import Records
    from taxcalc.gstrecords import GSTRecords
    from taxcalc.corprecords import CorpRecords
    from taxcalc.parameters import ParametersBase
    from taxcalc.calculator import Calculator
    

    """
    for num in range(1, num_reforms):
        block_selected_dict[num]['selected_item']= block_widget_dict[num][1].get()
        block_selected_dict[num]['selected_value']= block_widget_dict[num][3].get()
        block_selected_dict[num]['selected_year']= block_widget_dict[num][2].get()
    print(block_selected_dict)
    """
    f = open('reform.json')
    block_selected_dict = json.load(f)
    print("block_selected_dict from json",block_selected_dict)
    #print(block_selected_dict)
    # create Records object containing pit.csv and pit_weights.csv input data
    #print("growfactors filename ", growfactors_filename)
    #recs = Records(data=data_filename, weights=weights_filename, gfactors=GrowFactors(growfactors_filename=growfactors_filename))
    #recs = Records(data=data_filename, weights=weights_filename, gfactors=GrowFactors(growfactors_filename=growfactors_filename))

    #recs.increment_year1(3.0)
    
    #grecs = GSTRecords()
    f = open('global_vars.json')
    vars = json.load(f)
        
    print("data_filename: ", vars['cit_data_filename'])
    print("weights_filename: ", vars['cit_weights_filename'])
    print("growfactors_filename: ", vars['GROWFACTORS_FILENAME'])
    print("policy_filename: ", vars['DEFAULTS_FILENAME'])
    # create CorpRecords object using cross-section data
    #crecs1 = CorpRecords(data='cit_cross.csv', weights='cit_cross_wgts1.csv')
    crecs1 = CorpRecords(data=vars['cit_data_filename'], weights=vars['cit_weights_filename'], gfactors=GrowFactors(growfactors_filename=vars['GROWFACTORS_FILENAME']))
    #crecs1 = CorpRecords(data=vars['cit_weights_filename'], weights=vars['cit_weights_filename'])

    # Note: weights argument is optional
    assert isinstance(crecs1, CorpRecords)
    assert crecs1.current_year == 2017
    
    # create Policy object containing current-law policy
    pol = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])
    
    # specify Calculator objects for current-law policy
    #calc1 = Calculator(policy=pol, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)
    calc1 = Calculator(policy=pol, corprecords=crecs1, verbose=False)    
    #calc1.increment_year1(3.8)
    assert isinstance(calc1, Calculator)
    assert calc1.current_year == 2017

    np.seterr(divide='ignore', invalid='ignore')
    
    pol2 = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])
    
    years, reform=read_reform_dict(block_selected_dict)
    print("reform dictionary: ",reform) 
    #reform = Calculator.read_json_param_objects('app01_reform.json', None)
    pol2.implement_reform(reform['policy'])
    
    #calc2 = Calculator(policy=pol2, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)
    calc2 = Calculator(policy=pol2, corprecords=crecs1, verbose=False)
    pit_adjustment_factor={}
    revenue_dict_cit={}
    revenue_amount_dict = {}

    calc1.calc_all()
        
           
    for year in range(2019, 2024):
        cols = []
        calc1.advance_to_year(year)       
        calc2.advance_to_year(year)
        # NOTE: calc1 now contains a PRIVATE COPY of pol and a PRIVATE COPY of recs,
        #       so we can continue to use pol and recs in this script without any
        #       concern about side effects from Calculator method calls on calc1.

        # Produce DataFrame of results using the calculator
        
        # First run the calculator for the corporate income tax
        calc1.calc_all()
        
        print("***** Year ", year)
        weighted_citax1 = calc1.weighted_total_cit('citax')                
        citax_collection_billions1 = weighted_citax1/10**9       
        citax_collection_str1 = '{0:.2f}'.format(citax_collection_billions1)
              
        print("The CIT Collection in billions is: ", citax_collection_billions1)
 
        # Produce DataFrame of results using cross-section
        calc2.calc_all()
       
        weighted_citax2 = calc2.weighted_total_cit('citax')                
        citax_collection_billions2 = weighted_citax2/10**9    
        citax_collection_str2 = '{0:.2f}'.format(citax_collection_billions2)
        # This is the difference in the collection due to the reform
        # This amount will now be allocated to dividends of PIT
        citax_diff_collection_billions2 = (citax_collection_billions2-citax_collection_billions1)
        citax_diff_collection_str2 = '{0:.2f}'.format(citax_diff_collection_billions2)
              
        print("The CIT Collection after reform billions is: ", citax_collection_billions2)

        print("The difference in CIT Collection in billions is: ", citax_diff_collection_billions2)

        # Process of allocation of difference in CIT profits to PIT 
        # in the form of Dividends
        # Dividends in this case is reported as Income from Other Sources
        # TOTAL_INCOME_OS in the PIT form
        
        # First get the unadjusted amounts
        
        # Now calculate the adjusted amounts
        # contribution to PIT Dividends
        proportion_change_dividend = (weighted_citax1 - weighted_citax2)/weighted_citax1       
        new_dividend_proportion_of_old = (1 + proportion_change_dividend)
        pit_adjustment_factor[year]=new_dividend_proportion_of_old
        # Store Results
        revenue_dict_cit[year]={}
        revenue_dict_cit[year]['current_law']=citax_collection_str1
        revenue_dict_cit[year]['reform']=citax_collection_str2      
        revenue_dict_cit[year]['difference']=citax_diff_collection_str2
        
    print(revenue_dict_cit)
    
    print("new_dividend_proportion_of_old ", pit_adjustment_factor)     

    # now update pit.csv with this proportion
    # start a new round of simulation for pit
    recs = Records(data=vars['pit_data_filename'], weights=vars['pit_weights_filename'], gfactors=GrowFactors(growfactors_filename=vars['GROWFACTORS_FILENAME']))
    
    # create Policy object containing current-law policy
    pol = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])
    
    # specify Calculator objects for current-law policy
    #calc1 = Calculator(policy=pol, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)
    calc1 = Calculator(policy=pol, records=recs, verbose=False)    
    #calc1.increment_year1(3.8)
    assert isinstance(calc1, Calculator)
    assert calc1.current_year == 2017

    np.seterr(divide='ignore', invalid='ignore')
    
    pol2 = Policy(DEFAULTS_FILENAME=vars['DEFAULTS_FILENAME'])
    
    #years, reform=read_reform_dict(block_selected_dict)
    #print("reform dictionary: ", reform) 
    #reform = Calculator.read_json_param_objects('app01_reform.json', None)
    pol2.implement_reform(reform['policy'])
    
    #calc2 = Calculator(policy=pol2, records=recs, corprecords=crecs1,
    #                   gstrecords=grecs, verbose=False)

    calc2 = Calculator(policy=pol2, records=recs, verbose=False)
        
    total_revenue_text={}
    reform_revenue_text={}
    revenue_dict_pit={}
    revenue_amount_dict = {}
    num = 1
    first_time = True
    i=1
    j=0
    #rows = []
    
    window = tk.Toplevel()
    window.geometry("800x400+140+140")
    display_table(window, revenue_dict_cit, revenue_dict_pit, header=True)

    #for year in range(years[0], years[-1]+1):            
    for year in range(2019, 2024):
        cols = []
        calc1.advance_to_year(year)       
        calc2.advance_to_year(year)
        # NOTE: calc1 now contains a PRIVATE COPY of pol and a PRIVATE COPY of recs,
        #       so we can continue to use pol and recs in this script without any
        #       concern about side effects from Calculator method calls on calc1.

        # Produce DataFrame of results using the calculator
        
        # First run the calculator for the corporate income tax
        calc1.calc_all()
        
        weighted_pitax1 = calc1.weighted_total_pit('pitax')                
        pitax_collection_billions1 = weighted_pitax1/10**9        
        pitax_collection_str1 = '{0:.2f}'.format(pitax_collection_billions1)
        
        print('\n\n\n')
        print(f'TAX COLLECTION FOR THE YEAR - {year} \n')   
        print("The PIT Collection in billions is: ", pitax_collection_billions1)       
        #total_revenue_text[year] = "PIT COLLECTION UNDER CURRENT LAW FOR THE YEAR - " + str(year)+" : "+str(pitax_collection_str1)+" bill"

        # Produce DataFrame of results using cross-section
        calc2.calc_all()
       
        
        weighted_pitax2 = calc2.weighted_total_pit('pitax')
        pitax_collection_billions2 = weighted_pitax2/10**9        
        pitax_collection_str2 = '{0:.2f}'.format(pitax_collection_billions2)
        pitax_diff_collection_billions2 = (pitax_collection_billions2-pitax_collection_billions1)        
        pitax_diff_collection_str2 = '{0:.2f}'.format(pitax_diff_collection_billions2)
        
        # Now calculate the adjusted amounts
        # contribution to PIT Dividends

        print("Total Income from Other Sources (bill) no adjustment is ", calc2.weighted_total_pit('TOTAL_INCOME_OS')/10**9 )       
        calc2.adjust_pit(pit_adjustment_factor[year])
        print("Total Income from Other Sources (bill) after adjustment is ", calc2.weighted_total_pit('TOTAL_INCOME_OS')/10**9 )
        
        calc2.calc_all()
        
        weighted_pitax3 = calc2.weighted_total_pit('pitax')     
        pitax_collection_billions3 = weighted_pitax3/10**9        
        pitax_collection_str3 = '{0:.2f}'.format(pitax_collection_billions3)

        pitax_diff_collection_billions3 = (pitax_collection_billions3-pitax_collection_billions1)        
        pitax_diff_collection_str3 = '{0:.2f}'.format(pitax_diff_collection_billions3)

        pitax_diff_collection_billions4 = (pitax_collection_billions3-pitax_collection_billions2)        
        pitax_diff_collection_str4 = '{0:.2f}'.format(pitax_diff_collection_billions4)
        
        #save the results
        revenue_dict_pit[year]={}
        revenue_dict_pit[year]['current_law']=pitax_collection_str1
        revenue_dict_pit[year]['reform']={}
        revenue_dict_pit[year]['reform']['unadjusted']=pitax_collection_str2
        revenue_dict_pit[year]['reform']['adjusted']=pitax_collection_str3
        revenue_dict_pit[year]['difference']=pitax_diff_collection_str3
        
        print('\n\n\n')       
        print(f'TAX COLLECTION FOR THE YEAR UNDER REFORM - {year} \n')       
        print("The PIT Collection in billions is: ", pitax_collection_billions2)
        print("The difference in PIT Collection in billions is: ", pitax_diff_collection_billions2)
        print('****AFTER ADJUSTMENT \n\n\n')
        print('TAX COLLECTION FOR THE YEAR UNDER REFORM WITH ADJUSTMENT \n')       
        print("The PIT Collection in billions after adjusting for the impact of CIT is: ", pitax_collection_billions3)        
        print("The difference in PIT Collection in billions after adjusting for the impact of CIT is: ", pitax_diff_collection_billions3)

        print("The impact of adjustment is: ", pitax_diff_collection_billions4)
        
        display_table(window, revenue_dict_cit, revenue_dict_pit, year=year, row=i)
        i=i+1
        #reverse the adjustment to obtain baseline
        calc2.adjust_pit(1/pit_adjustment_factor[year])
   
    display_table(window, revenue_dict_cit, revenue_dict_pit, footer=i)

    
    
    """
Example #17
0
class Policy(Parameters):
    """
    Policy is a subclass of the abstract Parameters class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: GrowFactors class instance
        containing price inflation rates and wage growth rates

    Raises
    ------
    ValueError:
        if gfactors is not a GrowFactors class instance or None.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILE_NAME = 'policy_current_law.json'
    DEFAULTS_FILE_PATH = os.path.abspath(os.path.dirname(__file__))
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2019  # last year for which indexed param vals are known
    # should increase LAST_KNOWN_YEAR by one every calendar year
    LAST_BUDGET_YEAR = 2031  # last extrapolation year
    # should increase LAST_BUDGET_YEAR by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    # NOTE: the following three data structures use internal parameter names:
    # (1) specify which Policy parameters have been removed or renamed
    REMOVED_PARAMS = {
        # following five parameters removed in PR 2223 merged on 2019-02-06
        'DependentCredit_Child_c': 'is a removed parameter name',
        'DependentCredit_Nonchild_c': 'is a removed parameter name',
        'DependentCredit_before_CTC': 'is a removed parameter name',
        'FilerCredit_c': 'is a removed parameter name',
        'ALD_InvInc_ec_base_RyanBrady': 'is a removed parameter name',
        # TODO: following parameter renamed in PR 2292 merged on 2019-04-15
        "cpi_offset": (
            "was renamed parameter_indexing_CPI_offset. "
            "See documentation for change in usage."
        ),
        "CPI_offset": (
            "was renamed parameter_indexing_CPI_offset. "
            "See documentation for change in usage."
        ),
        # TODO: following parameters renamed in PR 2345 merged on 2019-06-24
        'PT_excl_rt':
        'was renamed PT_qbid_rt in release 2.4.0',
        'PT_excl_wagelim_thd':
        'was renamed PT_qbid_taxinc_thd in release 2.4.0',
        'PT_excl_wagelim_prt':
        'was renamed PT_qbid_taxinc_gap in release 2.4.0',
        'PT_excl_wagelim_rt':
        'was renamed PT_qbid_w2_wages_rt in release 2.4.0',
        'CTC_c_under5_bonus': 'was renamed CTC_c_under6_bonus.',
        'ACTC_rt_bonus_under5family':
        'was renamed ACTC_rt_bonus_under6family.',
        'CTC_new_c_under5_bonus': 'was renamed CTC_new_c_under6_bonus.'
    }
    # (2) specify which Policy parameters have been redefined recently
    REDEFINED_PARAMS = {}
    # (3) specify which Policy parameters are wage (rather than price) indexed
    WAGE_INDEXED_PARAMS = ['SS_Earnings_c', 'SS_Earnings_thd']

    def __init__(self, gfactors=None, only_reading_defaults=False, **kwargs):
        # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary
        super().__init__()
        # handle gfactors argument
        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')
        # read default parameters and initialize
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = None
        self._wage_growth_rates = None
        self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR,
                        Policy.REMOVED_PARAMS,
                        Policy.REDEFINED_PARAMS,
                        Policy.WAGE_INDEXED_PARAMS, **kwargs)

    @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):
        """
        Implement reform using Tax-Calculator syled reforms/adjustments. Users
        may also use the adjust method with ParamTools styled reforms.
        """
        # need to do conversion:
        return self._update(reform, print_warnings, raise_errors)

    @staticmethod
    def parameter_list():
        """
        Returns list of parameter names in the policy_current_law.json file.
        """
        path = os.path.join(
            Policy.DEFAULTS_FILE_PATH,
            Policy.DEFAULTS_FILE_NAME
        )
        with open(path) as f:
            defaults = json.loads(f.read())  # pylint: disable=protected-access
        return [k for k in defaults if k != "schema"]

    def set_rates(self):
        """Initialize taxcalc indexing data."""
        cpi_vals = [
            vo["value"] for
            vo in self._data["parameter_indexing_CPI_offset"]["value"]
        ]
        # extend parameter_indexing_CPI_offset values through budget window
        # if they have not been extended already.
        cpi_vals = cpi_vals + cpi_vals[-1:] * (
            self.end_year - self.start_year + 1 - len(cpi_vals)
        )
        cpi_offset = {
            (self.start_year + ix): val
            for ix, val in enumerate(cpi_vals)
        }

        self._gfactors = GrowFactors()

        self._inflation_rates = [
            np.round(rate + cpi_offset[self.start_year + ix], 4)
            for ix, rate in enumerate(
                self._gfactors.price_inflation_rates(
                    self.start_year, self.end_year
                )
            )
        ]

        self._wage_growth_rates = self._gfactors.wage_growth_rates(
            self.start_year, self.end_year
        )
Example #18
0
    def adjust_with_indexing(self, params_or_path, **kwargs):
        """
        Adjust parameter values with the following indexing logic:

        1. If "parameter_indexing_CPI_offset" is adjusted, first set
           parameter_indexing_CPI_offset to zero before implementing the
           adjusted parameter_indexing_CPI_offset to avoid stacking
           adjustments. Then, revert all values of indexed parameters to
           the 'known' values:

            a. The current values of parameters that are being adjusted are
               deleted after the first year in which
               parameter_indexing_CPI_offset is adjusted.
            b. The current values of parameters that are not being adjusted
               (i.e. are not in params) are deleted after the last known year,
               with the exception of parameters that revert to their pre-TCJA
               values in 2026. Instead, these (2026) parameter values are
               recalculated using the new inflation rates.

            After the 'unknown' values have been deleted, the last known value
            is extrapolated through the budget window. If there are indexed
            parameters in the adjustment, they will be included in the final
            adjustment call (unless their indexed status is changed).

        2. If the "indexed" status is updated for any parameter:

            a. If a parameter has values that are being adjusted before
               the indexed status is adjusted, update those parameters first.
            b. Extend the values of that parameter to the year in which
               the status is changed.
            c. Change the indexed status for the parameter.
            d. Update parameter values in adjustment that are adjusted after
               the year in which the indexed status changes.
            e. Using the new "-indexed" status, extend the values of that
               parameter through the remaining years or until the -indexed
               status changes again.

        3. Update all parameters that are not indexing related, i.e. they are
           not "parameter_indexing_CPI_offset" or do not end with "-indexed".

        4. Return parsed adjustment with all adjustments, including "-indexed"
           parameters.

        Notable side-effects:

        - All values of a parameter whose indexed status is adjusted are
          wiped out after the year in which the value is adjusted for the
          same hard-coding reason.
        """
        # Temporarily turn off extra ops during the intermediary adjustments
        # so that expensive and unnecessary operations are not run.
        label_to_extend = self.label_to_extend
        array_first = self.array_first
        self.array_first = False
        self._gfactors = GrowFactors()

        params = self.read_params(params_or_path)

        # Check if parameter_indexing_CPI_offset is adjusted. If so, reset
        # values of all indexed parameters after year where
        # parameter_indexing_CPI_offset is changed. If
        # parameter_indexing_CPI_offset is changed multiple times, then
        # reset values after the first year in which the
        # parameter_indexing_CPI_offset is changed.
        needs_reset = []
        if params.get("parameter_indexing_CPI_offset") is not None:
            # Update parameter_indexing_CPI_offset with new value.
            cpi_adj = super().adjust(
                {
                    "parameter_indexing_CPI_offset":
                    params["parameter_indexing_CPI_offset"]
                }, **kwargs)
            # turn off extend now that parameter_indexing_CPI_offset
            # has been updated.
            self.label_to_extend = None
            # Get first year in which parameter_indexing_CPI_offset
            # is changed.
            cpi_min_year = min(cpi_adj["parameter_indexing_CPI_offset"],
                               key=lambda vo: vo["year"])

            rate_adjustment_vals = (
                self.sel["parameter_indexing_CPI_offset"]["year"] >=
                cpi_min_year["year"])
            # "Undo" any existing parameter_indexing_CPI_offset for
            # years after parameter_indexing_CPI_offset has
            # been updated.
            self._inflation_rates = self._inflation_rates[:cpi_min_year[
                "year"] - self.start_year] + self._gfactors.price_inflation_rates(
                    cpi_min_year["year"], self.LAST_BUDGET_YEAR)

            # Then apply new parameter_indexing_CPI_offset values to
            # inflation rates
            for cpi_vo in rate_adjustment_vals:
                self._inflation_rates[cpi_vo["year"] -
                                      self.start_year] += cpi_vo["value"]
            # 1. Delete all unknown values.
            # 1.a For revision, these are years specified after cpi_min_year.
            to_delete = {}
            for param in params:
                if (param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if param.endswith("-indexed"):
                    param = param.split("-indexed")[0]
                if self._data[param].get("indexed", False):
                    to_delete[param] = (self.sel[param]["year"] >
                                        cpi_min_year["year"])
                    needs_reset.append(param)
            self.delete(to_delete, **kwargs)

            # 1.b For all others, these are years after last_known_year.
            last_known_year = max(cpi_min_year["year"], self._last_known_year)
            # calculate 2026 value, using new inflation rates, for parameters
            # that revert to their pre-TCJA values.
            long_params = [
                'II_brk7', 'II_brk6', 'II_brk5', 'II_brk4', 'II_brk3',
                'II_brk2', 'II_brk1', 'PT_brk7', 'PT_brk6', 'PT_brk5',
                'PT_brk4', 'PT_brk3', 'PT_brk2', 'PT_brk1',
                'PT_qbid_taxinc_thd', 'ALD_BusinessLosses_c', 'STD', 'II_em',
                'II_em_ps', 'AMT_em', 'AMT_em_ps', 'AMT_em_pe', 'ID_ps',
                'ID_AllTaxes_c'
            ]
            final_ifactor = 1.0
            pyear = 2017  # prior year before TCJA first implemented
            fyear = 2026  # final year in which parameter values revert to
            # pre-TCJA values
            # construct final-year inflation factor from prior year
            # NOTE: pvalue[t+1] = pvalue[t] * ( 1 + irate[t] )
            for year in range(pyear, fyear):
                final_ifactor *= 1 + \
                    self._inflation_rates[year - self.start_year]

            long_param_vals = defaultdict(list)
            # compute final year parameter value
            for param in long_params:
                # only revert param in 2026 if it's not in revision
                if params.get(param) is None:
                    # grab param values from 2017
                    vos = self.sel[param]["year"] == pyear
                    # use final_ifactor to inflate from 2017 to 2026
                    for vo in vos:
                        long_param_vals[param].append(
                            # Create new dict to avoid modifying the original
                            dict(
                                vo,
                                value=min(
                                    9e99, round(vo["value"] * final_ifactor,
                                                0)),
                                year=fyear,
                            ))
                    needs_reset.append(param)
            super().adjust(long_param_vals, **kwargs)

            to_delete = {}
            for param in self._data:
                if (param in params or param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if self._data[param].get("indexed", False):
                    to_delete[param] = self.sel[param]["_auto"] == True  # noqa
                    needs_reset.append(param)

            self.delete(to_delete, **kwargs)

            self.extend(label="year")

        # 2. Handle -indexed parameters.
        self.label_to_extend = None
        index_affected = set([])
        for param, values in params.items():
            if param.endswith("-indexed"):
                base_param = param.split("-indexed")[0]
                if not self._data[base_param].get("indexable", None):
                    msg = f"Parameter {base_param} is not indexable."
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                index_affected |= {param, base_param}
                indexed_changes = {}
                if isinstance(values, bool):
                    indexed_changes[self.start_year] = values
                elif isinstance(values, list):
                    for vo in values:
                        indexed_changes[vo.get("year",
                                               self.start_year)] = vo["value"]
                else:
                    msg = ("Index adjustment parameter must be a boolean or "
                           "list.")
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                # 2.a Adjust values less than first year in which index status
                # was changed.
                if base_param in params:
                    min_index_change_year = min(indexed_changes.keys())
                    vos = self.sel[params[base_param]]["year"].lt(
                        min_index_change_year, strict=False)

                    if len(list(vos)):
                        min_adj_year = min(vos,
                                           key=lambda vo: vo["year"])["year"]
                        self.delete({
                            base_param:
                            self.sel[base_param]["year"] > min_adj_year  # noqa
                        })
                        super().adjust({base_param: vos}, **kwargs)
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(
                                range(self.start_year, min_index_change_year)),
                        )

                for year in sorted(indexed_changes):
                    indexed_val = indexed_changes[year]
                    # Get and delete all default values after year where
                    # indexed status changed.
                    self.delete(
                        {base_param: self.sel[base_param]["year"] > year})

                    # 2.b Extend values for this parameter to the year where
                    # the indexed status changes.
                    if year > self.start_year:
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(range(self.start_year,
                                                    year + 1)),
                        )

                    # 2.c Set indexed status.
                    self._data[base_param]["indexed"] = indexed_val

                    # 2.d Adjust with values greater than or equal to current
                    # year in params
                    if base_param in params:
                        vos = self.sel[params[base_param]]["year"].gte(
                            year, strict=False)
                        super().adjust({base_param: vos}, **kwargs)

                    # 2.e Extend values through remaining years.
                    self.extend(params=[base_param], label="year")

                needs_reset.append(base_param)
        # Re-instate ops.
        self.label_to_extend = label_to_extend
        self.array_first = array_first
        self.set_state()

        # Filter out "-indexed" params.
        nonindexed_params = {
            param: val
            for param, val in params.items() if param not in index_affected
        }

        # 3. Do adjustment for all non-indexing related parameters.
        adj = super().adjust(nonindexed_params, **kwargs)

        # 4. Add indexing params back for return to user.
        adj.update({
            param: val
            for param, val in params.items() if param in index_affected
        })
        return adj
Example #19
0
class Policy(Parameters):
    """
    Policy is a subclass of the abstract Parameters class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: GrowFactors class instance
        containing price inflation rates and wage growth rates

    Raises
    ------
    ValueError:
        if gfactors is not a GrowFactors class instance or None.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILE_NAME = 'policy_current_law.json'
    DEFAULTS_FILE_PATH = os.path.abspath(os.path.dirname(__file__))
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2018  # last year for which indexed param vals are known
    # should increase LAST_KNOWN_YEAR by one every calendar year
    LAST_BUDGET_YEAR = 2029  # last extrapolation year
    # should increase LAST_BUDGET_YEAR by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    # NOTE: the following three data structures use internal parameter names:
    # (1) specify which Policy parameters have been removed or renamed
    REMOVED_PARAMS = {
        # following five parameters removed in PR 2223 merged on 2019-02-06
        '_DependentCredit_Child_c': 'is a removed parameter name',
        '_DependentCredit_Nonchild_c': 'is a removed parameter name',
        '_DependentCredit_before_CTC': 'is a removed parameter name',
        '_FilerCredit_c': 'is a removed parameter name',
        '_ALD_InvInc_ec_base_RyanBrady': 'is a removed parameter name',
        # TODO: following parameter renamed in PR 2292 merged on 2019-04-??
        '_cpi_offset': 'was renamed CPI_offset in release 2.0.0'
    }
    # (2) specify which Policy parameters have been redefined recently
    REDEFINED_PARAMS = {
        # TODO: remove the CTC_c name:message pair sometime later in 2019
        '_CTC_c': 'CTC_c was redefined in release 1.0.0'
    }
    # (3) specify which Policy parameters are wage (rather than price) indexed
    WAGE_INDEXED_PARAMS = [
        '_SS_Earnings_c',
        '_SS_Earnings_thd'
    ]

    def __init__(self, gfactors=None, only_reading_defaults=False):
        # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary
        super().__init__()
        if only_reading_defaults:
            return
        # handle gfactors argument
        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')
        # read default parameters and initialize
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
        self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR,
                        Policy.REMOVED_PARAMS,
                        Policy.REDEFINED_PARAMS,
                        Policy.WAGE_INDEXED_PARAMS)

    def inflation_rates(self):
        """
        Returns list of price inflation rates starting with JSON_START_YEAR.
        """
        return self._inflation_rates

    def wage_growth_rates(self):
        """
        Returns list of wage growth rates starting with JSON_START_YEAR.
        """
        return self._wage_growth_rates

    def implement_reform(self, reform,
                         print_warnings=True, raise_errors=True):
        """
        Implement specified policy reform and leave current_year unchanged.
        See Parameters._update for argument documentation and details about
        the expected structure of the reform dictionary.
        """
        self._update(reform, print_warnings, raise_errors)

    @staticmethod
    def parameter_list():
        """
        Returns list of parameter names in the policy_current_law.json file.
        """
        policy = Policy(only_reading_defaults=True)
        plist = list(policy._vals.keys())  # pylint: disable=protected-access
        del policy
        return plist
Example #20
0
class Policy(Parameters):
    """
    Policy is a subclass of the abstract Parameters class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: GrowFactors class instance
        containing price inflation rates and wage growth rates

    Raises
    ------
    ValueError:
        if gfactors is not a GrowFactors class instance or None.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILE_NAME = 'policy_current_law.json'
    DEFAULTS_FILE_PATH = os.path.abspath(os.path.dirname(__file__))
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2018  # last year for which indexed param vals are known
    # should increase LAST_KNOWN_YEAR by one every calendar year
    LAST_BUDGET_YEAR = 2029  # last extrapolation year
    # should increase LAST_BUDGET_YEAR by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    # NOTE: the following three data structures use internal parameter names:
    # (1) specify which Policy parameters have been removed or renamed
    REMOVED_PARAMS = {
        # following five parameters removed in PR 2223 merged on 2019-02-06
        '_DependentCredit_Child_c': 'is a removed parameter name',
        '_DependentCredit_Nonchild_c': 'is a removed parameter name',
        '_DependentCredit_before_CTC': 'is a removed parameter name',
        '_FilerCredit_c': 'is a removed parameter name',
        '_ALD_InvInc_ec_base_RyanBrady': 'is a removed parameter name',
        # TODO: following parameter renamed in PR 2292 merged on 2019-04-15
        '_cpi_offset': 'was renamed CPI_offset in release 2.0.0',
        # TODO: following parameters renamed in PR 2345 merged on 2019-06-24
        '_PT_excl_rt': 'was renamed PT_qbid_rt in release 2.4.0',
        '_PT_excl_wagelim_thd':
        'was renamed PT_qbid_taxinc_thd in release 2.4.0',
        '_PT_excl_wagelim_prt':
        'was renamed PT_qbid_taxinc_gap in release 2.4.0',
        '_PT_excl_wagelim_rt':
        'was renamed PT_qbid_w2_wages_rt in release 2.4.0'
    }
    # (2) specify which Policy parameters have been redefined recently
    REDEFINED_PARAMS = {
        # TODO: remove the CTC_c name:message pair sometime later in 2019
        '_CTC_c': 'CTC_c was redefined in release 1.0.0'
    }
    # (3) specify which Policy parameters are wage (rather than price) indexed
    WAGE_INDEXED_PARAMS = ['_SS_Earnings_c', '_SS_Earnings_thd']

    def __init__(self, gfactors=None, only_reading_defaults=False):
        # put JSON contents of DEFAULTS_FILE_NAME into self._vals dictionary
        super().__init__()
        if only_reading_defaults:
            return
        # handle gfactors argument
        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')
        # read default parameters and initialize
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
        self.initialize(syr, nyrs, Policy.LAST_KNOWN_YEAR,
                        Policy.REMOVED_PARAMS, Policy.REDEFINED_PARAMS,
                        Policy.WAGE_INDEXED_PARAMS)

    def inflation_rates(self):
        """
        Returns list of price inflation rates starting with JSON_START_YEAR.
        """
        return self._inflation_rates

    def wage_growth_rates(self):
        """
        Returns list of wage growth rates starting with JSON_START_YEAR.
        """
        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):
        """
        Implement specified policy reform and leave current_year unchanged.
        See Parameters._update for argument documentation and details about
        the expected structure of the reform dictionary.
        """
        self._update(reform, print_warnings, raise_errors)

    @staticmethod
    def parameter_list():
        """
        Returns list of parameter names in the policy_current_law.json file.
        """
        policy = Policy(only_reading_defaults=True)
        plist = list(policy._vals.keys())  # pylint: disable=protected-access
        del policy
        return plist
 def __init__(self,
              data='puf.csv',
              start_year=PUFCSV_YEAR,
              gfactors=GrowFactors(),
              weights=PUF_WEIGHTS_FILENAME,
              adjust_ratios=PUF_RATIOS_FILENAME,
              exact_calculations=False):
     # pylint: disable=no-member,too-many-branches
     if isinstance(weights, str):
         weights = os.path.join(Records.CODE_PATH, weights)
     super().__init__(data, start_year, gfactors, weights)
     if data is None:
         return  # because there are no data
     # read adjustment ratios
     self.ADJ = None
     self._read_ratios(adjust_ratios)
     # specify exact value based on exact_calculations
     self.exact[:] = np.where(exact_calculations is True, 1, 0)
     # specify FLPDYR value based on start_year
     self.FLPDYR.fill(start_year)
     # check for valid MARS values
     if not np.all(np.logical_and(np.greater_equal(self.MARS, 1),
                                  np.less_equal(self.MARS, 5))):
         raise ValueError('not all MARS values in [1,5] range')
     # create variables derived from MARS, which is in MUST_READ_VARS
     self.num[:] = np.where(self.MARS == 2, 2, 1)
     self.sep[:] = np.where(self.MARS == 3, 2, 1)
     # check for valid EIC values
     if not np.all(np.logical_and(np.greater_equal(self.EIC, 0),
                                  np.less_equal(self.EIC, 3))):
         raise ValueError('not all EIC values in [0,3] range')
     # check that three sets of split-earnings variables have valid values
     msg = 'expression "{0} == {0}p + {0}s" is not true for every record'
     tol = 0.020001  # handles "%.2f" rounding errors
     if not np.allclose(self.e00200, (self.e00200p + self.e00200s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00200'))
     if not np.allclose(self.e00900, (self.e00900p + self.e00900s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00900'))
     if not np.allclose(self.e02100, (self.e02100p + self.e02100s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e02100'))
     # check that spouse income variables have valid values
     nospouse = self.MARS != 2
     zeros = np.zeros_like(self.MARS[nospouse])
     msg = '{} is not always zero for non-married filing unit'
     if not np.allclose(self.e00200s[nospouse], zeros):
         raise ValueError(msg.format('e00200s'))
     if not np.allclose(self.e00900s[nospouse], zeros):
         raise ValueError(msg.format('e00900s'))
     if not np.allclose(self.e02100s[nospouse], zeros):
         raise ValueError(msg.format('e02100s'))
     if not np.allclose(self.k1bx14s[nospouse], zeros):
         raise ValueError(msg.format('k1bx14s'))
     # check that ordinary dividends are no less than qualified dividends
     other_dividends = np.maximum(0., self.e00600 - self.e00650)
     if not np.allclose(self.e00600, self.e00650 + other_dividends,
                        rtol=0.0, atol=tol):
         msg = 'expression "e00600 >= e00650" is not true for every record'
         raise ValueError(msg)
     del other_dividends
     # check that total pension income is no less than taxable pension inc
     nontaxable_pensions = np.maximum(0., self.e01500 - self.e01700)
     if not np.allclose(self.e01500, self.e01700 + nontaxable_pensions,
                        rtol=0.0, atol=tol):
         msg = 'expression "e01500 >= e01700" is not true for every record'
         raise ValueError(msg)
     del nontaxable_pensions
     # check that PT_SSTB_income has valid value
     if not np.all(np.logical_and(np.greater_equal(self.PT_SSTB_income, 0),
                                  np.less_equal(self.PT_SSTB_income, 1))):
         raise ValueError('not all PT_SSTB_income values are 0 or 1')
Example #22
0
class Policy(Parameters):
    """
    Policy is a subclass of the abstract Parameters class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: GrowFactors class instance
        containing price inflation rates and wage growth rates

    Raises
    ------
    ValueError:
        if gfactors is not a GrowFactors class instance or None.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILENAME = 'policy_current_law.json'
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2017  # last year for which indexed param vals are known
    # should increase LAST_KNOWN_YEAR by one every calendar year
    LAST_BUDGET_YEAR = 2027  # last extrapolation year
    # should increase LAST_BUDGET_YEAR by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    def __init__(self, gfactors=None):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = GrowFactors()
        elif isinstance(gfactors, GrowFactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a GrowFactors instance')

        # read default parameters and initialize
        self._vals = self._params_dict_from_json_file()
        syr = Policy.JSON_START_YEAR
        lyr = Policy.LAST_BUDGET_YEAR
        nyrs = Policy.DEFAULT_NUM_YEARS
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], nyrs)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)
        self.initialize(syr, nyrs)

        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._ignore_errors = False

    def inflation_rates(self):
        """
        Returns list of price inflation rates starting with JSON_START_YEAR.
        """
        return self._inflation_rates

    def wage_growth_rates(self):
        """
        Returns list of wage growth rates starting with JSON_START_YEAR.
        """
        return self._wage_growth_rates

    def implement_reform(self, reform,
                         print_warnings=False, raise_errors=True):
        """
        Implement multi-year policy reform and leave current_year unchanged.

        Parameters
        ----------
        reform: dictionary of one or more YEAR:MODS pairs
            see Notes to Parameters _update method for info on MODS structure

        print_warnings: boolean
            if True, prints warnings when parameter_warnings exists;
            if False, does not print warnings when parameter_warnings exists
                    and leaves warning handling to caller of implement_reform.

        raise_errors: boolean
            if True, raises ValueError when parameter_errors exists;
            if False, does not raise ValueError when parameter_errors exists
                    and leaves error handling to caller of implement_reform.

        Raises
        ------
        ValueError:
            if reform is not a dictionary.
            if each YEAR in reform is not an integer.
            if minimum YEAR in the YEAR:MODS pairs is less than start_year.
            if minimum YEAR in the YEAR:MODS pairs is less than current_year.
            if maximum YEAR in the YEAR:MODS pairs is greater than end_year.
            if raise_errors is True AND
              _validate_parameter_names_types generates errors OR
              _validate_parameter_values generates errors.

        Returns
        -------
        nothing: void

        Notes
        -----
        Given a reform dictionary, typical usage of the Policy class
        is as follows::

            policy = Policy()
            policy.implement_reform(reform)

        In the above statements, the Policy() call instantiates a
        policy object (policy) containing current-law policy parameters,
        and the implement_reform(reform) call applies the (possibly
        multi-year) reform specified in reform and then sets the
        current_year to the value of current_year when implement_reform
        was called with parameters set for that pre-call year.

        An example of a multi-year, multi-parameter reform is as follows::

            reform = {
                2016: {
                    '_EITC_c': [[900, 5000, 8000, 9000]],
                    '_II_em': [7000],
                    '_SS_Earnings_c': [300000]
                },
                2017: {
                    '_SS_Earnings_c': [500000], '_SS_Earnings_c_cpi': False
                },
                2019: {
                    '_EITC_c': [[1200, 7000, 10000, 12000]],
                    '_II_em': [9000],
                    '_SS_Earnings_c': [700000], '_SS_Earnings_c_cpi': True
                }
            }

        Notice that each of the four YEAR:MODS pairs is specified as
        required by the private _update method, whose documentation
        provides several MODS dictionary examples.

        IMPORTANT NOTICE: when specifying a reform dictionary always group
        all reform provisions for a specified year into one YEAR:MODS pair.
        If you make the mistake of specifying two or more YEAR:MODS pairs
        with the same YEAR value, all but the last one will be overwritten,
        and therefore, not part of the reform.  This is because Python
        expects unique (not multiple) dictionary keys.  There is no way to
        catch this error, so be careful to specify reform dictionaries
        correctly.
        """
        # check that all reform dictionary keys are integers
        if not isinstance(reform, dict):
            raise ValueError('ERROR: YYYY PARAM reform is not a dictionary')
        if not reform:
            return  # no reform to implement
        reform_years = sorted(list(reform.keys()))
        for year in reform_years:
            if not isinstance(year, int):
                msg = 'ERROR: {} KEY {}'
                details = 'KEY in reform is not an integer calendar year'
                raise ValueError(msg.format(year, details))
        # check range of remaining reform_years
        first_reform_year = min(reform_years)
        if first_reform_year < self.start_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < start_year={}'
            raise ValueError(msg.format(first_reform_year, self.start_year))
        if first_reform_year < self.current_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < current_year={}'
            raise ValueError(msg.format(first_reform_year, self.current_year))
        last_reform_year = max(reform_years)
        if last_reform_year > self.end_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR > end_year={}'
            raise ValueError(msg.format(last_reform_year, self.end_year))
        # validate reform parameter names and types
        self.parameter_warnings = ''
        self.parameter_errors = ''
        self._validate_parameter_names_types(reform)
        if not self._ignore_errors and self.parameter_errors:
            raise ValueError(self.parameter_errors)
        # optionally apply cpi_offset to inflation_rates and re-initialize
        if Policy._cpi_offset_in_reform(reform):
            known_years = self._apply_reform_cpi_offset(reform)
            self.set_default_vals(known_years=known_years)
        # implement the reform year by year
        precall_current_year = self.current_year
        reform_parameters = set()
        for year in reform_years:
            self.set_year(year)
            reform_parameters.update(reform[year].keys())
            self._update({year: reform[year]})
        self.set_year(precall_current_year)
        # validate reform parameter values
        self._validate_parameter_values(reform_parameters)
        if self.parameter_warnings and print_warnings:
            print(self.parameter_warnings)
        if self.parameter_errors and raise_errors:
            raise ValueError('\n' + self.parameter_errors)

    JSON_REFORM_SUFFIXES = {
        # MARS-indexed suffixes and list index numbers
        'single': 0,
        'joint': 1,
        'separate': 2,
        'headhousehold': 3,
        'widow': 4,
        # EIC-indexed suffixes and list index numbers
        '0kids': 0,
        '1kid': 1,
        '2kids': 2,
        '3+kids': 3,
        # idedtype-indexed suffixes and list index numbers
        'medical': 0,
        'statelocal': 1,
        'realestate': 2,
        'casualty': 3,
        'misc': 4,
        'interest': 5,
        'charity': 6
    }

    @staticmethod
    def translate_json_reform_suffixes(indict,
                                       growdiff_baseline_dict,
                                       growdiff_response_dict):
        """
        Replace any array parameters with suffixes in the specified
        JSON-derived "policy" dictionary, indict, and
        return a JSON-equivalent dictionary containing constructed array
        parameters and containing no parameters with suffixes, odict.
        """
        # pylint: disable=too-many-statements
        # define no_suffix function used only in this method
        def no_suffix(idict):
            """
            Return param_base:year dictionary having only no-suffix parameters.
            """
            odict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix not in suffixes:
                    odict[param] = idict[param]
            return odict

        # define group_dict function used only in this method
        def suffix_group_dict(idict):
            """
            Return param_base:year:suffix dictionary with each idict value.
            """
            gdict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix in suffixes:
                    del param_pieces[-1]
                    param_base = '_'.join(param_pieces)
                    if param_base not in gdict:
                        gdict[param_base] = dict()
                    for year in sorted(idict[param].keys()):
                        if year not in gdict[param_base]:
                            gdict[param_base][year] = dict()
                        gdict[param_base][year][suffix] = idict[param][year][0]
            return gdict

        # define with_suffix function used only in this method
        def with_suffix(gdict, growdiff_baseline_dict, growdiff_response_dict):
            """
            Return param_base:year dictionary having only suffix parameters.
            """
            if bool(growdiff_baseline_dict) or bool(growdiff_response_dict):
                gdiff_baseline = GrowDiff()
                gdiff_baseline.update_growdiff(growdiff_baseline_dict)
                gdiff_response = GrowDiff()
                gdiff_response.update_growdiff(growdiff_response_dict)
                growfactors = GrowFactors()
                gdiff_baseline.apply_to(growfactors)
                gdiff_response.apply_to(growfactors)
            else:
                gdiff_baseline = None
                gdiff_response = None
                growfactors = None
            pol = Policy(gfactors=growfactors)
            pol.ignore_reform_errors()
            odict = dict()
            for param in gdict.keys():
                odict[param] = dict()
                for year in sorted(gdict[param].keys()):
                    odict[param][year] = dict()
                    for suffix in gdict[param][year].keys():
                        plist = getattr(pol, param).tolist()
                        dvals = plist[int(year) - Policy.JSON_START_YEAR]
                        odict[param][year] = [dvals]
                        idx = Policy.JSON_REFORM_SUFFIXES[suffix]
                        odict[param][year][0][idx] = gdict[param][year][suffix]
                        udict = {int(year): {param: odict[param][year]}}
                        pol.implement_reform(udict,
                                             print_warnings=False,
                                             raise_errors=False)
            del gdiff_baseline
            del gdiff_response
            del growfactors
            del pol
            return odict

        # high-level logic of translate_json_reform_suffixes method:
        # - construct odict containing just parameters without a suffix
        odict = no_suffix(indict)
        # - group params with suffix into param_base:year:suffix dictionary
        gdict = suffix_group_dict(indict)
        # - add to odict the consolidated values for parameters with a suffix
        if gdict:
            odict.update(with_suffix(gdict,
                                     growdiff_baseline_dict,
                                     growdiff_response_dict))
        # - return policy dictionary containing constructed parameter arrays
        return odict

    def ignore_reform_errors(self):
        """
        Sets self._ignore_errors to True.
        """
        self._ignore_errors = True

    def metadata(self):
        """
        Returns ordered dictionary of parameter information based on
        the contents of the policy_current_law.json file with updates
        to each parameter's 'start_year', 'row_label', and 'value' key
        values so that the updated values contain just the current_year
        information for this instance of the Policy class.
        """
        mdata = collections.OrderedDict()
        for pname, pdata in self._vals.items():
            mdata[pname] = pdata
            mdata[pname]['row_label'] = ['{}'.format(self.current_year)]
            mdata[pname]['start_year'] = '{}'.format(self.current_year)
            valraw = getattr(self, pname[1:])
            if isinstance(valraw, np.ndarray):
                val = valraw.tolist()
            else:
                val = valraw
            mdata[pname]['value'] = [val]
        return mdata

    @staticmethod
    def parameter_list():
        """
        Returns list of parameter names in the policy_current_law.json file.
        """
        pdict = Policy._params_dict_from_json_file()
        plist = list(pdict.keys())
        del pdict
        return plist

    # ----- begin private methods of Policy class -----

    def _apply_clp_cpi_offset(self, cpi_offset_clp_data, num_years):
        """
        Call this method from Policy constructor
        after self._inflation_rates has been set and
        before base class initialize method is called.
        (num_years is number of years for which inflation rates are specified)
        """
        ovalues = cpi_offset_clp_data['value']
        if len(ovalues) < num_years:  # extrapolate last known value
            ovalues = ovalues + ovalues[-1:] * (num_years - len(ovalues))
        for idx in range(0, num_years):
            infrate = round(self._inflation_rates[idx] + ovalues[idx], 6)
            self._inflation_rates[idx] = infrate

    @staticmethod
    def _cpi_offset_in_reform(reform):
        """
        Return true if cpi_offset is in reform; otherwise return false.
        """
        for year in reform:
            for name in reform[year]:
                if name == '_cpi_offset':
                    return True
        return False

    def _apply_reform_cpi_offset(self, reform):
        """
        Call this method ONLY if _cpi_offset_in_reform returns True.
        Apply CPI offset to inflation rates and
        revert indexed parameter values in preparation for re-indexing.
        Also, return known_years which is
        (first cpi_offset year - start year + 1).
        """
        # extrapolate cpi_offset reform
        self.set_year(self.start_year)
        first_cpi_offset_year = 0
        for year in sorted(reform.keys()):
            self.set_year(year)
            if '_cpi_offset' in reform[year]:
                if first_cpi_offset_year == 0:
                    first_cpi_offset_year = year
                oreform = {'_cpi_offset': reform[year]['_cpi_offset']}
                self._update({year: oreform})
        self.set_year(self.start_year)
        assert first_cpi_offset_year > 0
        # adjust inflation rates
        cpi_offset = getattr(self, '_cpi_offset')
        for idx in range(0, self.num_years):
            infrate = round(self._inflation_rates[idx] + cpi_offset[idx], 6)
            self._inflation_rates[idx] = infrate
        # revert CPI-indexed parameter values to policy_current_law.json values
        for name in self._vals.keys():
            if self._vals[name]['cpi_inflated']:
                setattr(self, name, self._vals[name]['value'])
        # return known_years
        return first_cpi_offset_year - self.start_year + 1

    def _validate_parameter_names_types(self, reform):
        """
        Check validity of parameter names and parameter types used
        in the specified reform dictionary.
        """
        # pylint: disable=too-many-branches,too-many-nested-blocks
        # pylint: disable=too-many-locals
        param_names = set(self._vals.keys())
        for year in sorted(reform.keys()):
            for name in reform[year]:
                if name.endswith('_cpi'):
                    if isinstance(reform[year][name], bool):
                        pname = name[:-4]  # root parameter name
                        if pname not in param_names:
                            msg = '{} {} unknown parameter name'
                            self.parameter_errors += (
                                'ERROR: ' + msg.format(year, name) + '\n'
                            )
                        else:
                            # check if root parameter is cpi inflatable
                            if not self._vals[pname]['cpi_inflatable']:
                                msg = '{} {} parameter is not cpi inflatable'
                                self.parameter_errors += (
                                    'ERROR: ' + msg.format(year, pname) + '\n'
                                )
                    else:
                        msg = '{} {} parameter is not true or false'
                        self.parameter_errors += (
                            'ERROR: ' + msg.format(year, name) + '\n'
                        )
                else:  # if name does not end with '_cpi'
                    if name not in param_names:
                        msg = '{} {} unknown parameter name'
                        self.parameter_errors += (
                            'ERROR: ' + msg.format(year, name) + '\n'
                        )
                    else:
                        # check parameter value type avoiding use of isinstance
                        # because isinstance(True, (int,float)) is True, which
                        # makes it impossible to check float parameters
                        bool_param_type = self._vals[name]['boolean_value']
                        int_param_type = self._vals[name]['integer_value']
                        assert isinstance(reform[year][name], list)
                        pvalue = reform[year][name][0]
                        if isinstance(pvalue, list):
                            scalar = False  # parameter value is a list
                        else:
                            scalar = True  # parameter value is a scalar
                            pvalue = [pvalue]  # make scalar a single-item list
                        # pylint: disable=consider-using-enumerate
                        for idx in range(0, len(pvalue)):
                            if scalar:
                                pname = name
                            else:
                                pname = '{}_{}'.format(name, idx)
                            pval = pvalue[idx]
                            # pylint: disable=unidiomatic-typecheck
                            pval_is_bool = type(pval) == bool
                            pval_is_int = type(pval) == int
                            pval_is_float = type(pval) == float
                            if bool_param_type:
                                if not pval_is_bool:
                                    msg = '{} {} value {} is not boolean'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
                            elif int_param_type:
                                if not pval_is_int:
                                    msg = '{} {} value {} is not integer'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
                            else:  # param is float type
                                if not (pval_is_int or pval_is_float):
                                    msg = '{} {} value {} is not a number'
                                    self.parameter_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pval) +
                                        '\n'
                                    )
        del param_names

    def _validate_parameter_values(self, parameters_set):
        """
        Check values of parameters in specified parameter_set using
        range information from the policy_current_law.json file.
        """
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-nested-blocks
        parameters = sorted(parameters_set)
        syr = Policy.JSON_START_YEAR
        for pname in parameters:
            if pname.endswith('_cpi'):
                continue  # *_cpi parameter values validated elsewhere
            pvalue = getattr(self, pname)
            for vop, vval in self._vals[pname]['range'].items():
                if isinstance(vval, str):
                    vvalue = getattr(self, vval)
                else:
                    vvalue = np.full(pvalue.shape, vval)
                assert pvalue.shape == vvalue.shape
                assert len(pvalue.shape) <= 2
                if len(pvalue.shape) == 2:
                    scalar = False  # parameter value is a list
                else:
                    scalar = True  # parameter value is a scalar
                for idx in np.ndindex(pvalue.shape):
                    out_of_range = False
                    if vop == 'min' and pvalue[idx] < vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} < min value {}'
                        extra = self._vals[pname]['out_of_range_minmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if vop == 'max' and pvalue[idx] > vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} > max value {}'
                        extra = self._vals[pname]['out_of_range_maxmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if out_of_range:
                        action = self._vals[pname]['out_of_range_action']
                        if scalar:
                            name = pname
                        else:
                            name = '{}_{}'.format(pname, idx[1])
                            if extra:
                                msg += '_{}'.format(idx[1])
                        if action == 'warn':
                            self.parameter_warnings += (
                                'WARNING: ' + msg.format(idx[0] + syr, name,
                                                         pvalue[idx],
                                                         vvalue[idx]) + '\n'
                            )
                        if action == 'stop':
                            self.parameter_errors += (
                                'ERROR: ' + msg.format(idx[0] + syr, name,
                                                       pvalue[idx],
                                                       vvalue[idx]) + '\n'
                            )
        del parameters
Example #23
0
class Parameters(pt.Parameters):
    """
    Base class that wraps ParamTools, providing parameter indexing
    for tax policy in the ``adjust`` method and convenience methods
    like ``set_year`` for classes inheriting from it. It also provides
    a backwards-compatible layer for Tax-Calculator versions prior to 3.0.

    The defaults file path may be set through the defaults class attribute
    variable or through the ``DEFAULTS_FILE_NAME`` /
    ``DEFAULTS_FILE_PATH work`` flow.

    A custom getter method is implemented so that the value of a parameter
    over all allowed years can conveniently be retrieved by adding an
    underscore before the variable name (e.g. ``EITC_c`` vs ``_EITC_c``).

    This class inherits methods from ParamTools like ``items``:

        .. code-block :: python

            import taxcalc as tc
            pol = tc.Policy()

            for name, value in pol.items():
                print(name, value)

            # parameter_indexing_CPI_offset [0.]
            # FICA_ss_trt [0.124]
            # SS_Earnings_c [113700.]

    Check out the ParamTools
    `documentation <https://paramtools.dev/api/reference.html>`_
    for more information on these inherited methods.

    """
    defaults = None
    array_first = True
    label_to_extend = "year"
    uses_extend_func = True

    REMOVED_PARAMS = None
    REDEFINED_PARAMS = None
    WAGE_INDEXED_PARAMS = ()

    # Legacy class attrs
    DEFAULTS_FILE_NAME = None
    DEFAULTS_FILE_PATH = None
    JSON_START_YEAR = None
    LAST_KNOWN_YEAR = None

    def __init__(self,
                 start_year=None,
                 num_years=None,
                 last_known_year=None,
                 removed=None,
                 redefined=None,
                 wage_indexed=None,
                 **kwargs):
        # In case we need to wait for this to be called from the
        # initialize method for legacy reasons.
        if not start_year or not num_years:
            return
        self._wage_growth_rates = None
        self._inflation_rates = None
        if (self.defaults is None and self.DEFAULTS_FILE_PATH is not None
                and self.DEFAULTS_FILE_NAME):
            self.defaults = os.path.join(self.DEFAULTS_FILE_PATH,
                                         self.DEFAULTS_FILE_NAME)

        if last_known_year is None:
            self._last_known_year = start_year
        else:
            assert last_known_year >= start_year
            assert last_known_year <= self.LAST_BUDGET_YEAR
            self._last_known_year = last_known_year

        self._removed_params = removed or self.REMOVED_PARAMS
        self._redefined_params = redefined or self.REDEFINED_PARAMS

        self._wage_indexed = wage_indexed or self.WAGE_INDEXED_PARAMS

        if ((start_year or self.JSON_START_YEAR)
                and "initial_state" not in kwargs):
            kwargs["initial_state"] = {
                "year": start_year or self.JSON_START_YEAR
            }
        super().__init__(**kwargs)

    def adjust(self,
               params_or_path,
               print_warnings=True,
               raise_errors=True,
               **kwargs):
        """
        Update parameter values using a ParamTools styled adjustment.

        Parameters
        ----------
        params_or_path : Dict, str
            New parameter values in the paramtools format. For example:

            .. code-block:: json

                {
                    "standard_deduction": [
                        {"year": 2024, "marital_status": "single", "value": 10000.0},
                        {"year": 2024, "marital_status": "joint", "value": 10000.0}
                    ],
                    "ss_rate": [{"year": 2024, "value": 0.2}]}
                }

        print_warnings : Boolean
            Print parameter warnings or not
        raise_errors: Boolean
            Raise errors as a ValidationError. If False, they will be stored
            in the errors attribute.


        Returns
        -------
        adjustment : Dict
            Parsed paremeter dictionary

        """  # noqa
        if print_warnings:
            _data = copy.deepcopy(self._data)
            kwargs["ignore_warnings"] = False
        else:
            kwargs["ignore_warnings"] = True
        self._warnings = {}
        self._errors = {}
        try:
            # Wrap all updates in adjust_with_indexing in a transaction and
            # defer related-parameter validation until all intermediate updates
            # are complete.
            with self.transaction(
                    defer_validation=True,
                    raise_errors=True,
                    ignore_warnings=kwargs["ignore_warnings"],
            ):
                return self.adjust_with_indexing(params_or_path,
                                                 raise_errors=True,
                                                 **kwargs)
        except pt.ValidationError as ve:
            if self.errors and raise_errors:
                raise ve
            elif self.errors and not raise_errors:
                return {}
            if print_warnings:
                print("WARNING:")
                print(self.warnings)
            kwargs["ignore_warnings"] = True
            self._data = _data
            _warnings = copy.deepcopy(self._warnings)
            self._warnings = {}
            self._errors = {}
            adjustment = self.adjust_with_indexing(params_or_path,
                                                   raise_errors=True,
                                                   **kwargs)
            self._warnings = _warnings
            return adjustment

    def adjust_with_indexing(self, params_or_path, **kwargs):
        """
        Adjust parameter values with the following indexing logic:

        1. If "parameter_indexing_CPI_offset" is adjusted, first set
           parameter_indexing_CPI_offset to zero before implementing the
           adjusted parameter_indexing_CPI_offset to avoid stacking
           adjustments. Then, revert all values of indexed parameters to
           the 'known' values:

            a. The current values of parameters that are being adjusted are
               deleted after the first year in which
               parameter_indexing_CPI_offset is adjusted.
            b. The current values of parameters that are not being adjusted
               (i.e. are not in params) are deleted after the last known year,
               with the exception of parameters that revert to their pre-TCJA
               values in 2026. Instead, these (2026) parameter values are
               recalculated using the new inflation rates.

            After the 'unknown' values have been deleted, the last known value
            is extrapolated through the budget window. If there are indexed
            parameters in the adjustment, they will be included in the final
            adjustment call (unless their indexed status is changed).

        2. If the "indexed" status is updated for any parameter:

            a. If a parameter has values that are being adjusted before
               the indexed status is adjusted, update those parameters first.
            b. Extend the values of that parameter to the year in which
               the status is changed.
            c. Change the indexed status for the parameter.
            d. Update parameter values in adjustment that are adjusted after
               the year in which the indexed status changes.
            e. Using the new "-indexed" status, extend the values of that
               parameter through the remaining years or until the -indexed
               status changes again.

        3. Update all parameters that are not indexing related, i.e. they are
           not "parameter_indexing_CPI_offset" or do not end with "-indexed".

        4. Return parsed adjustment with all adjustments, including "-indexed"
           parameters.

        Notable side-effects:

        - All values of a parameter whose indexed status is adjusted are
          wiped out after the year in which the value is adjusted for the
          same hard-coding reason.
        """
        # Temporarily turn off extra ops during the intermediary adjustments
        # so that expensive and unnecessary operations are not run.
        label_to_extend = self.label_to_extend
        array_first = self.array_first
        self.array_first = False
        self._gfactors = GrowFactors()

        params = self.read_params(params_or_path)

        # Check if parameter_indexing_CPI_offset is adjusted. If so, reset
        # values of all indexed parameters after year where
        # parameter_indexing_CPI_offset is changed. If
        # parameter_indexing_CPI_offset is changed multiple times, then
        # reset values after the first year in which the
        # parameter_indexing_CPI_offset is changed.
        needs_reset = []
        if params.get("parameter_indexing_CPI_offset") is not None:
            # Update parameter_indexing_CPI_offset with new value.
            cpi_adj = super().adjust(
                {
                    "parameter_indexing_CPI_offset":
                    params["parameter_indexing_CPI_offset"]
                }, **kwargs)
            # turn off extend now that parameter_indexing_CPI_offset
            # has been updated.
            self.label_to_extend = None
            # Get first year in which parameter_indexing_CPI_offset
            # is changed.
            cpi_min_year = min(cpi_adj["parameter_indexing_CPI_offset"],
                               key=lambda vo: vo["year"])

            rate_adjustment_vals = (
                self.sel["parameter_indexing_CPI_offset"]["year"] >=
                cpi_min_year["year"])
            # "Undo" any existing parameter_indexing_CPI_offset for
            # years after parameter_indexing_CPI_offset has
            # been updated.
            self._inflation_rates = self._inflation_rates[:cpi_min_year[
                "year"] - self.start_year] + self._gfactors.price_inflation_rates(
                    cpi_min_year["year"], self.LAST_BUDGET_YEAR)

            # Then apply new parameter_indexing_CPI_offset values to
            # inflation rates
            for cpi_vo in rate_adjustment_vals:
                self._inflation_rates[cpi_vo["year"] -
                                      self.start_year] += cpi_vo["value"]
            # 1. Delete all unknown values.
            # 1.a For revision, these are years specified after cpi_min_year.
            to_delete = {}
            for param in params:
                if (param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if param.endswith("-indexed"):
                    param = param.split("-indexed")[0]
                if self._data[param].get("indexed", False):
                    to_delete[param] = (self.sel[param]["year"] >
                                        cpi_min_year["year"])
                    needs_reset.append(param)
            self.delete(to_delete, **kwargs)

            # 1.b For all others, these are years after last_known_year.
            last_known_year = max(cpi_min_year["year"], self._last_known_year)
            # calculate 2026 value, using new inflation rates, for parameters
            # that revert to their pre-TCJA values.
            long_params = [
                'II_brk7', 'II_brk6', 'II_brk5', 'II_brk4', 'II_brk3',
                'II_brk2', 'II_brk1', 'PT_brk7', 'PT_brk6', 'PT_brk5',
                'PT_brk4', 'PT_brk3', 'PT_brk2', 'PT_brk1',
                'PT_qbid_taxinc_thd', 'ALD_BusinessLosses_c', 'STD', 'II_em',
                'II_em_ps', 'AMT_em', 'AMT_em_ps', 'AMT_em_pe', 'ID_ps',
                'ID_AllTaxes_c'
            ]
            final_ifactor = 1.0
            pyear = 2017  # prior year before TCJA first implemented
            fyear = 2026  # final year in which parameter values revert to
            # pre-TCJA values
            # construct final-year inflation factor from prior year
            # NOTE: pvalue[t+1] = pvalue[t] * ( 1 + irate[t] )
            for year in range(pyear, fyear):
                final_ifactor *= 1 + \
                    self._inflation_rates[year - self.start_year]

            long_param_vals = defaultdict(list)
            # compute final year parameter value
            for param in long_params:
                # only revert param in 2026 if it's not in revision
                if params.get(param) is None:
                    # grab param values from 2017
                    vos = self.sel[param]["year"] == pyear
                    # use final_ifactor to inflate from 2017 to 2026
                    for vo in vos:
                        long_param_vals[param].append(
                            # Create new dict to avoid modifying the original
                            dict(
                                vo,
                                value=min(
                                    9e99, round(vo["value"] * final_ifactor,
                                                0)),
                                year=fyear,
                            ))
                    needs_reset.append(param)
            super().adjust(long_param_vals, **kwargs)

            to_delete = {}
            for param in self._data:
                if (param in params or param == "parameter_indexing_CPI_offset"
                        or param in self._wage_indexed):
                    continue
                if self._data[param].get("indexed", False):
                    to_delete[param] = self.sel[param]["_auto"] == True  # noqa
                    needs_reset.append(param)

            self.delete(to_delete, **kwargs)

            self.extend(label="year")

        # 2. Handle -indexed parameters.
        self.label_to_extend = None
        index_affected = set([])
        for param, values in params.items():
            if param.endswith("-indexed"):
                base_param = param.split("-indexed")[0]
                if not self._data[base_param].get("indexable", None):
                    msg = f"Parameter {base_param} is not indexable."
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                index_affected |= {param, base_param}
                indexed_changes = {}
                if isinstance(values, bool):
                    indexed_changes[self.start_year] = values
                elif isinstance(values, list):
                    for vo in values:
                        indexed_changes[vo.get("year",
                                               self.start_year)] = vo["value"]
                else:
                    msg = ("Index adjustment parameter must be a boolean or "
                           "list.")
                    raise pt.ValidationError({"errors": {
                        base_param: msg
                    }},
                                             labels=None)
                # 2.a Adjust values less than first year in which index status
                # was changed.
                if base_param in params:
                    min_index_change_year = min(indexed_changes.keys())
                    vos = self.sel[params[base_param]]["year"].lt(
                        min_index_change_year, strict=False)

                    if len(list(vos)):
                        min_adj_year = min(vos,
                                           key=lambda vo: vo["year"])["year"]
                        self.delete({
                            base_param:
                            self.sel[base_param]["year"] > min_adj_year  # noqa
                        })
                        super().adjust({base_param: vos}, **kwargs)
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(
                                range(self.start_year, min_index_change_year)),
                        )

                for year in sorted(indexed_changes):
                    indexed_val = indexed_changes[year]
                    # Get and delete all default values after year where
                    # indexed status changed.
                    self.delete(
                        {base_param: self.sel[base_param]["year"] > year})

                    # 2.b Extend values for this parameter to the year where
                    # the indexed status changes.
                    if year > self.start_year:
                        self.extend(
                            params=[base_param],
                            label="year",
                            label_values=list(range(self.start_year,
                                                    year + 1)),
                        )

                    # 2.c Set indexed status.
                    self._data[base_param]["indexed"] = indexed_val

                    # 2.d Adjust with values greater than or equal to current
                    # year in params
                    if base_param in params:
                        vos = self.sel[params[base_param]]["year"].gte(
                            year, strict=False)
                        super().adjust({base_param: vos}, **kwargs)

                    # 2.e Extend values through remaining years.
                    self.extend(params=[base_param], label="year")

                needs_reset.append(base_param)
        # Re-instate ops.
        self.label_to_extend = label_to_extend
        self.array_first = array_first
        self.set_state()

        # Filter out "-indexed" params.
        nonindexed_params = {
            param: val
            for param, val in params.items() if param not in index_affected
        }

        # 3. Do adjustment for all non-indexing related parameters.
        adj = super().adjust(nonindexed_params, **kwargs)

        # 4. Add indexing params back for return to user.
        adj.update({
            param: val
            for param, val in params.items() if param in index_affected
        })
        return adj

    def get_index_rate(self, param, label_to_extend_val):
        """
        Initalize indexing data and return the indexing rate value
        depending on the parameter name and label_to_extend_val, the value of
        label_to_extend.
        Returns: rate to use for indexing.
        """
        if not self._inflation_rates or not self._wage_growth_rates:
            self.set_rates()
        if param in self._wage_indexed:
            return self.wage_growth_rates(year=label_to_extend_val)
        else:
            return self.inflation_rates(year=label_to_extend_val)

    def set_rates(self):
        """
        This method is implemented by classes inheriting
        Parameters.
        """
        raise NotImplementedError()

    def wage_growth_rates(self, year=None):
        if year is not None:
            return self._wage_growth_rates[year - self.start_year]
        return self._wage_growth_rates or []

    def inflation_rates(self, year=None):
        if year is not None:
            return self._inflation_rates[year - self.start_year]
        return self._inflation_rates or []

    # alias methods below
    def initialize(self,
                   start_year,
                   num_years,
                   last_known_year=None,
                   removed=None,
                   redefined=None,
                   wage_indexed=None,
                   **kwargs):
        """
        Legacy method for initializing a Parameters instance. Projects
        should use the __init__ method in the future.
        """
        # case where project hasn't been initialized yet.
        if getattr(self, "_data", None) is None:
            return Parameters.__init__(self,
                                       start_year,
                                       num_years,
                                       last_known_year=last_known_year,
                                       removed=removed,
                                       redefined=redefined,
                                       wage_indexed=wage_indexed,
                                       **kwargs)

    def _update(self, revision, print_warnings, raise_errors):
        """
        A translation layer on top of ``adjust``. Projects
        that have historically used the ``_update`` method with
        Tax-Calculator styled adjustments can continue to do so
        without making any changes to how they handle adjustments.

        Converts reforms that are compatible with Tax-Calculator:

        .. code-block:: python

            adjustment = {
                "standard_deduction": {2024: [10000.0, 10000.0]},
                "ss_rate": {2024: 0.2}
            }

        into reforms that are compatible with ParamTools:

        .. code-block:: python

            {
                "standard_deduction": [
                    {"year": 2024, "marital_status": "single", "value": 10000.0},
                    {"year": 2024, "marital_status": "joint", "value": 10000.0}
                ],
                "ss_rate": [{"year": 2024, "value": 0.2}]}
            }

        """  # noqa: E501
        if not isinstance(revision, dict):
            raise pt.ValidationError(
                {"errors": {
                    "schema": "Revision must be a dictionary."
                }}, None)
        new_params = defaultdict(list)
        for param, val in revision.items():
            if not isinstance(param, str):
                msg = f"Parameter {param} is not a string."
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
            if (param not in self._data
                    and param.split("-indexed")[0] not in self._data):
                if self._removed_params and param in self._removed_params:
                    msg = f"{param} {self._removed_params[param]}"
                elif (self._redefined_params
                      and param in self._redefined_params):
                    msg = self._redefined_params[param]
                else:
                    msg = f"Parameter {param} does not exist."
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
            if param.endswith("-indexed"):
                for year, yearval in val.items():
                    new_params[param] += [{"year": year, "value": yearval}]
            elif isinstance(val, dict):
                for year, yearval in val.items():
                    val = getattr(self, param)
                    if (self._data[param].get("type", None) == "str"
                            and isinstance(yearval, str)):
                        new_params[param] += [{"value": yearval}]
                        continue

                    yearval = np.array(yearval)
                    if (getattr(val, "shape", None)
                            and yearval.shape != val[0].shape):
                        exp_dims = val[0].shape
                        if exp_dims == tuple():
                            msg = (f"{param} is not an array " f"parameter.")
                        elif yearval.shape:
                            msg = (f"{param} has {yearval.shape[0]} elements "
                                   f"but should only have {exp_dims[0]} "
                                   f"elements.")
                        else:
                            msg = (f"{param} is an array parameter with "
                                   f"{exp_dims[0]} elements.")
                        raise pt.ValidationError({"errors": {
                            "schema": msg
                        }}, None)

                    value_objects = self.from_array(param,
                                                    yearval.reshape(
                                                        (1, *yearval.shape)),
                                                    year=year)
                    new_params[param] += value_objects
            else:
                msg = (f"{param} must be a year:value dictionary "
                       f"if you are not using the new adjust method.")
                raise pt.ValidationError({"errors": {"schema": msg}}, None)
        return self.adjust(new_params,
                           print_warnings=print_warnings,
                           raise_errors=raise_errors)

    def set_year(self, year):
        self.set_state(year=year)

    @property
    def current_year(self):
        return self.label_grid["year"][0]

    @property
    def start_year(self):
        return self._stateless_label_grid["year"][0]

    @property
    def end_year(self):
        return self._stateless_label_grid["year"][-1]

    @property
    def num_years(self):
        return self.end_year - self.start_year + 1

    @property
    def parameter_warnings(self):
        return self.errors or {}

    @property
    def parameter_errors(self):
        return self.errors or {}

    @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`` or ``Parameters.adjust`` methods.
        The obj function argument can be ``None`` or a string, where the
        string can be:

          - Path for a local file
          - Link pointing to a valid JSON file
          - 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``.

        Some examples of valid links are:

        - HTTP: ``https://raw.githubusercontent.com/PSLmodels/Tax-Calculator/master/taxcalc/reforms/2017_law.json``

        - Github API: ``github://PSLmodels:Tax-Calculator@master/taxcalc/reforms/2017_law.json``

        Checkout the ParamTools
        `docs <https://paramtools.dev/_modules/paramtools/parameters.html#Parameters.read_params>`_
        for more information on valid file URLs.
        """  # noqa

        # 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()

        full_dict = pt.read_json(obj)

        # check top-level key contents of dictionary
        if topkey in full_dict.keys():
            single_dict = full_dict[topkey]
        else:
            single_dict = full_dict

        if is_paramtools_format(single_dict):
            return single_dict

        # convert string year to integer year in dictionary and return
        return convert_year_to_int(single_dict)

    def metadata(self):
        return self.specification(meta_data=True, use_state=False)

    @staticmethod
    def years_in_revision(revision):
        """
        Return list of years in specified revision dictionary, which is
        assumed to have a param:year:value format.
        """
        assert isinstance(revision, dict)
        years = list()
        for _, paramdata in revision.items():
            assert isinstance(paramdata, dict)
            for year, _ in paramdata.items():
                assert isinstance(year, int)
                if year not in years:
                    years.append(year)
        return years

    def __getattr__(self, attr):
        """
        Get the value of a parameter over all years by accessing it
        with an underscore in front of its name: ``pol._EITC_c`` instead of
        ``pol.EITC_c``.
        """
        if (attr.startswith("_")
                and attr[1:] in super().__getattribute__("_data")):
            return self.to_array(attr[1:],
                                 year=list(
                                     range(self.start_year,
                                           self.end_year + 1)))
        else:
            raise AttributeError(f"{attr} not definied.")