class SimpleTaxIO(): """ Constructor for the simple tax input-output class. Parameters ---------- input_filename: string name of required INPUT file. reform: None or string or dictionary None implies no reform (current-law policy), or string is name of optional json REFORM file, or dictionary suitable for passing to Policy.implement_reform() method. exact_calculations: boolean specifies whether or not exact tax calculations are done without any smoothing of "stair-step" provisions in income tax law. emulate_taxsim_2441_logic: boolean true implies emulation of questionable Internet-TAXSIM logic, which is necessary if the SimpleTaxIO class is being used in validation tests against Internet-TAXSIM output. output_records: boolean true implies write a CSV-formatted file containing for each INPUT filing unit the TAXYEAR values of each variable in the Records.USABLE_READ_VARS set. Raises ------ ValueError: if file with input_filename is not a string or does not exist. if reform is neither None, string, nor dictionary. if earliest INPUT year before simtax start year. if latest INPUT year after simtax end year. Returns ------- class instance: SimpleTaxIO """ def __init__(self, input_filename, reform, exact_calculations, emulate_taxsim_2441_logic, output_records): """ SimpleTaxIO class constructor. """ # pylint: disable=too-many-arguments # check that input_filename is a string if not isinstance(input_filename, str): msg = 'SimpleTaxIO.ctor input_filename is not a string' raise ValueError(msg) # construct output_filename and delete old output file if it exists # ... construct reform extension to output_filename if reform is None: ref = '' self._using_reform_file = True else: # if reform is not None if isinstance(reform, str): if reform.endswith('.json'): ref = '-{}'.format(reform[:-5]) else: ref = '-{}'.format(reform) self._using_reform_file = True elif isinstance(reform, dict): ref = '' self._using_reform_file = False else: msg = 'SimpleTaxIO.ctor reform is neither None, str, nor dict' raise ValueError(msg) # ... construct whole output_filename self._using_input_file = True self._output_filename = '{}.out-simtax{}'.format(input_filename, ref) if os.path.isfile(self._output_filename): os.remove(self._output_filename) # pragma: no cover # check for existence of file named input_filename if not os.path.isfile(input_filename): msg = 'INPUT file named {} could not be found' raise ValueError(msg.format(input_filename)) # read input file contents into self._input dictionary self._read_input(input_filename) self.policy = Policy() # implement reform if reform is specified if reform: if self._using_reform_file: param_dict = Calculator.read_json_param_objects(reform, None) r_pol = param_dict['policy'] else: r_pol = reform self.policy.implement_reform(r_pol) # validate input variable values self._validate_input() self.calc = self._calc_object(exact_calculations, emulate_taxsim_2441_logic, output_records) def start_year(self): """ Returns starting year for SimpleTaxIO calculations """ return self.policy.start_year def end_year(self): """ Returns ending year for SimpleTaxIO calculations """ return self.policy.end_year def calculate(self, writing_output_file=False, exact_output=False): """ Calculate taxes for all INPUT lines and write or return OUTPUT lines. Output lines will be written to file if SimpleTaxIO constructor was passed an input_filename string and a reform string or None and if writing_output_file is True. Parameters ---------- writing_output_file: boolean exact_output: boolean Returns ------- output_lines: string empty string if OUTPUT lines are written to a file; otherwise output_lines contain all OUTPUT lines """ # pylint: disable=too-many-locals # loop through self._year_set doing tax calculations and saving output output = {} # dictionary indexed by Records index for filing unit for calcyr in self._year_set: if calcyr != self.calc.policy_current_year(): self.calc.policy_current_year(calcyr) self.calc.records_current_year(calcyr) self.calc.calc_all() (mtr_ptax, mtr_itax, _) = self.calc.mtr(wrt_full_compensation=False) cr_taxyr = self.calc.array('FLPDYR') for idx in range(0, self.calc.array_len): indyr = cr_taxyr[idx] if indyr == calcyr: ovar = SimpleTaxIO.extract_output(self.calc, idx, exact=exact_output) ovar[7] = 100 * mtr_itax[idx] ovar[9] = 100 * mtr_ptax[idx] output[idx] = ovar assert len(output) == len(self._input) # handle disposition of calculated output olines = '' writing_possible = self._using_input_file and self._using_reform_file if writing_possible and writing_output_file: SimpleTaxIO.write_output_file( output, self._output_filename) # pragma: no cover else: idx_range = range(0, len(output)) for idx in idx_range: olines += SimpleTaxIO.construct_output_line(output[idx]) return olines def number_input_lines(self): """ Return number of lines read from INPUT file. """ return len(self._input) @staticmethod def show_iovar_definitions(): """ Write definitions of INPUT and OUTPUT variables to stdout. Parameters ---------- none: void Returns ------- nothing: void """ ivd = ('**** SimpleTaxIO INPUT variables in Internet-TAXSIM format:\n' 'Note that each INPUT variable must be an integer.\n' '(NEG) notation means that variable can be negative;\n' ' otherwise variable must be non-negative.\n' '[ 1] arbitrary id of income tax filing unit\n' '[ 2] calendar year of income tax filing\n' '[ 3] state code [MUST BE ZERO]\n' '[ 4] filing status [MUST BE 1, 2, or 3]\n' ' 1=single, 2=married_filing_jointly, 3=head_of_household\n' '[ 5] total number of dependents\n' '[ 6] taxpayer/spouse age code: 100*taxpayer_age+spouse_age\n' ' where spouse_age is zero when there is no spouse\n' '[ 7] taxpayer wage and salary income\n' '[ 8] spouse wage and salary income\n' '[ 9] qualified dividend income\n' '[10] other property income (NEG)\n' '[11] pension benefits that are federally taxable\n' '[12] total social security (OASDI) benefits\n' '[13] other non-fed-taxable transfer income [MUST BE ZERO]\n' '[14] rent paid [MUST BE ZERO]\n' '[15] real-estate taxes paid: an AMT-preference item\n' '[16] other itemized deductions that are AMT-preference items\n' '[17] child care expenses\n' '[18] unemployment (UI) benefits received\n' '[19] number of dependents under age 17\n' '[20] itemized deductions that are not AMT-preference items\n' '[21] short-term capital gains or losses (NEG)\n' '[22] long-term capital gains or losses (NEG)\n') sys.stdout.write(ivd) ovd = ('**** SimpleTaxIO OUTPUT variables in Internet-TAXSIM format:\n' '[ 1] arbitrary id of income tax filing unit\n' '[ 2] calendar year of income tax filing\n' '[ 3] state code [ALWAYS ZERO]\n' '[ 4] federal income tax liability\n' '[ 5] state income tax liability [ALWAYS ZERO]\n' '[ 6] OASDI+HI payroll tax liability (sum of ee and er share)\n' '[ 7] marginal federal income tax rate as percent\n' '[ 8] marginal state income tax rate [ALWAYS ZERO]\n' '[ 9] marginal payroll tax rate as percent\n' '[10] federal adjusted gross income, AGI\n' '[11] unemployment (UI) benefits included in AGI\n' '[12] social security (OASDI) benefits included in AGI\n' '[13] [ALWAYS ZERO]\n' '[14] personal exemption after phase-out\n' '[15] phased-out (i.e., disallowed) personal exemption\n' '[16] phased-out (i.e., disallowed) itemized deduction\n' '[17] itemized deduction after phase-out ' '(zero for non-itemizer)\n' '[18] federal regular taxable income\n' '[19] regular tax on regular taxable income ' '(no special capital gains rates)\n' ' EXCEPT use special rates WHEN --exact OPTION SPECIFIED\n' '[20] [ALWAYS ZERO]\n' '[21] [ALWAYS ZERO]\n' '[22] child tax credit (adjusted)\n' '[23] child tax credit (refunded)\n' '[24] credit for child care expenses\n' '[25] federal earned income tax credit, EITC\n' '[26] federal AMT taxable income\n' '[27] federal AMT liability\n' '[28] federal income tax (excluding AMT) before credits\n') sys.stdout.write(ovd) @staticmethod def extract_output(calc, idx, exact=False, extract_weight=False): """ Extracts tax output from Calculator object for one tax filing unit. Parameters ---------- calc: Calculator Calculator object containing embedded Records object. idx: integer Records array index of the one tax filing unit. exact: boolean whether or not ovar[19] is exact regular tax on regular income. extract_weight: boolean whether or not to extract s006 sample weight into ovar[29]. Returns ------- ovar: dictionary of output variables indexed from 1 to OVAR_NUM, or from 1 to OVAR_NUM+1 if extract_weight is True, of from 1 to OVAR_NUM+? if debugging variables are included. Notes ----- The value of each output variable is stored in the ovar dictionary, which is indexed as Internet-TAXSIM output variables are (where the index begins with one). """ ovar = {} ovar[1] = calc.array('RECID')[idx] # id for tax filing unit ovar[2] = calc.array('FLPDYR')[idx] # year taxes are calculated ovar[3] = 0 # state code is always zero ovar[4] = calc.array('iitax')[idx] # federal income tax liability ovar[5] = 0.0 # no state income tax calculation ovar[6] = calc.array('payrolltax')[idx] # ee+er for OASDI+HI ovar[7] = 0.0 # marginal federal income tax rate as percent ovar[8] = 0.0 # no state income tax calculation ovar[9] = 0.0 # marginal payroll tax rate as percent ovar[10] = calc.array('c00100')[idx] # federal AGI ovar[11] = calc.array('e02300')[idx] # UI benefits in AGI ovar[12] = calc.array('c02500')[idx] # OASDI benefits in AGI ovar[13] = 0.0 # always set zero-bracket amount to zero pre_phase_out_pe = calc.array('pre_c04600')[idx] post_phase_out_pe = calc.array('c04600')[idx] phased_out_pe = pre_phase_out_pe - post_phase_out_pe ovar[14] = post_phase_out_pe # post-phase-out personal exemption ovar[15] = phased_out_pe # personal exemption that is phased out # ovar[16] can be positive for non-itemizer: ovar[16] = calc.array('c21040')[idx] # phased out itemized deduction # ovar[17] is zero for non-itemizer: ovar[17] = calc.array('c04470')[idx] # post-phase-out item deduction ovar[18] = calc.array('c04800')[idx] # federal regular taxable income # ovar[19] is regular tax on taxable income if exact: ovar[19] = calc.array('taxbc')[idx] else: # Internet-TAXSIM ovar[19] that ignores special qdiv+ltcg rates ovar[19] = calc.array('c05200')[idx] # tax from Sch X,Y,Z tables ovar[20] = 0.0 # always set exemption surtax to zero ovar[21] = 0.0 # always set general tax credit to zero ovar[22] = calc.array('c07220')[idx] # child tax credit (adjusted) ovar[23] = calc.array('c11070')[idx] # extra refunded child tax credit ovar[24] = calc.array('c07180')[idx] # child care credit ovar[25] = calc.array('eitc')[idx] # federal EITC ovar[26] = calc.array('c62100')[idx] # federal AMT taxable income amt_liability = calc.array('c09600')[idx] # federal AMT liability ovar[27] = amt_liability # ovar[28] is federal income tax before credits; the Tax-Calculator # calc.array('c05800')[idx] is this concept but includes AMT liability # while Internet-TAXSIM ovar[28] explicitly excludes AMT liability, so # we have the following: ovar[28] = calc.array('c05800')[idx] - amt_liability # add optional weight and debugging output to ovar dictionary if extract_weight: ovar[29] = calc.array('s006')[idx] # sample weight num = SimpleTaxIO.OVAR_NUM + 1 else: num = SimpleTaxIO.OVAR_NUM for dvar_name in SimpleTaxIO.DVAR_NAMES: num += 1 dvar = calc.array(dvar_name) if dvar is None: msg = '{} "{}" {}'.format( # pragma: no cover 'debugging variable name', dvar_name, 'not in Records object') raise ValueError(msg) # pragma: no cover else: ovar[num] = dvar[idx] return ovar @staticmethod def write_output_file(output, output_filename): """ Write all output to file with output_filename. Parameters ---------- output: dictionary of OUTPUT variables for each INPUT tax filing unit output_filename: string Returns ------- nothing: void """ with open(output_filename, 'w') as output_file: # pragma: no cover idx_range = range(0, len(output)) # pragma: no cover for idx in idx_range: # pragma: no cover outline = SimpleTaxIO.construct_output_line( output[idx]) # pragma: no cover output_file.write(outline) # pragma: no cover OVAR_NUM = 28 DVAR_NAMES = [ # OPTIONAL DEBUGGING OUTPUT VARIABLE NAMES # '...', # first debugging variable # '...', # second debugging variable # etc. # '...' # last debugging variable ] OVAR_FMT = {1: '{:d}.', # add decimal point as in Internet-TAXSIM output 2: ' {:.0f}', 3: ' {:d}', 4: ' {:.2f}', 5: ' {:.2f}', 6: ' {:.2f}', 7: ' {:.2f}', 8: ' {:.2f}', 9: ' {:.2f}', 10: ' {:.2f}', 11: ' {:.2f}', 12: ' {:.2f}', 13: ' {:.2f}', 14: ' {:.2f}', 15: ' {:.2f}', 16: ' {:.2f}', 17: ' {:.2f}', 18: ' {:.2f}', 19: ' {:.2f}', 20: ' {:.2f}', 21: ' {:.2f}', 22: ' {:.2f}', 23: ' {:.2f}', 24: ' {:.2f}', 25: ' {:.2f}', 26: ' {:.2f}', 27: ' {:.2f}', 28: ' {:.2f}'} @staticmethod def construct_output_line(output_dict): """ Construct line of OUTPUT from a filing unit output_dict. Parameters ---------- output_dict: dictionary calculated output values indexed from 1 to len(output_dict). Returns ------- output_line: string """ outline = '' for vnum in range(1, len(output_dict) + 1): fnum = min(vnum, SimpleTaxIO.OVAR_NUM) outline += SimpleTaxIO.OVAR_FMT[fnum].format(output_dict[vnum]) outline += '\n' return outline # --- begin private methods of SimpleTaxIO class --- # IVAR_NUM = 22 IVAR_NONNEG = {1: True, 2: True, 3: True, 4: True, 5: True, 6: True, 7: True, 8: True, 9: True, 10: False, 11: True, 12: True, 13: True, 14: True, 15: True, 16: True, 17: True, 18: True, 19: True, 20: True, 21: False, 22: False} # True ==> value must be non-negative def _read_input(self, input_filename): """ Read INPUT and save input variables in self._input dictionary. Parameters ---------- input_filename: string name of simtax INPUT file. Raises ------ ValueError: if INPUT variables are not in Internet-TAXSIM format. if INPUT variables have improper numeric type or value. Returns ------- nothing: void Notes ----- The integer value of each input variable is stored in the self._input dictionary, which is indexed by INPUT file line number and by INPUT file variable number (where both indexes begin with one). """ self._input = {} # indexed by line number and by variable number lnum = 0 used_var1_strings = set() with open(input_filename, 'r') as input_file: for line in input_file: lnum += 1 istrlist = line.split() if len(istrlist) != SimpleTaxIO.IVAR_NUM: msg = ('simtax INPUT line {} has {} not ' '{} space-delimited variables') raise ValueError(msg.format(lnum, len(istrlist), SimpleTaxIO.IVAR_NUM)) vnum = 0 vardict = {} for istr in istrlist: vnum += 1 # convert istr to integer value try: val = int(istr) except ValueError: msg = ('simtax INPUT line {} variable {} has ' 'value {} that is not an integer') raise ValueError(msg.format(lnum, vnum, istr)) # check val for proper value range if SimpleTaxIO.IVAR_NONNEG[vnum]: if val < 0: msg = ('var[{}]={} on line {} of simtax INPUT has ' 'a negative value') raise ValueError(msg.format(vnum, val, lnum)) # check that var[1] is unique in INPUT file if vnum == 1: if istr in used_var1_strings: msg = ('var[1]={} on line {} of simtax INPUT has ' 'already been used') raise ValueError(msg.format(istr, lnum)) else: used_var1_strings.add(istr) # add val for vnum to vardict vardict[vnum] = val # add lnum:vardict pair to self._input dictionary self._input[lnum] = vardict def _validate_input(self): """ Validate INPUT variable values stored in self._input dictionary. Parameters ---------- none: void Raises ------ ValueError: if INPUT variable values are not in proper range of values. Returns ------- nothing: void Notes ----- The integer value of each input variable is stored in the self._input dictionary, which is indexed by INPUT file line number and by INPUT file variable number (where both indexes begin with one). """ self._year_set = set() min_year = self.start_year() max_year = self.end_year() for lnum in range(1, len(self._input) + 1): var = self._input[lnum] year = var[2] if year < min_year or year > max_year: msg = ('var[2]={} on line {} of simtax INPUT is not in ' '[{},{}] Policy start-year, end-year range') raise ValueError(msg.format(year, lnum, min_year, max_year)) if year not in self._year_set: self._year_set.add(year) state = var[3] if state != 0: msg = ('var[3]={} on line {} of simtax INPUT is not zero ' 'to indicate no state income tax calculations') raise ValueError(msg.format(state, lnum)) filing_status = var[4] if filing_status < 1 or filing_status > 3: msg = ('var[4]={} on line {} of simtax INPUT is not ' 'in [1,3] filing-status range') raise ValueError(msg.format(filing_status, lnum)) num_all_dependents = var[5] if filing_status == 3 and num_all_dependents == 0: msg = ('var[5]={} on line {} of simtax INPUT is not ' 'positive when var[4] equals 3') raise ValueError(msg.format(num_all_dependents, lnum)) agecode = var[6] if agecode < 100: # using old Internet-TAXSIM agecode if ((filing_status == 2 and agecode > 2) or (filing_status != 2 and agecode > 1)): msg = ('var[6]={} on line {} of simtax INPUT is has ' 'illegal value') raise ValueError(msg.format(agecode, lnum)) else: # using new Internet-TAXSIM agecode pass transfers = var[13] if transfers != 0: msg = ('var[13]={} on line {} of simtax INPUT is not zero ' 'to indicate no state income tax calculations') raise ValueError(msg.format(transfers, lnum)) rent = var[14] if rent != 0: msg = ('var[14]={} on line {} of simtax INPUT is not zero ' 'to indicate no state income tax calculations') raise ValueError(msg.format(rent, lnum)) num_young_dependents = var[19] if num_young_dependents > num_all_dependents: msg = ('var[19]={} on line {} of simtax INPUT is not less ' 'than or equal to var[5]={}') raise ValueError(msg.format(num_young_dependents, lnum, num_all_dependents)) def _calc_object(self, exact_calcs, emulate_taxsim_2441_logic, output_records): """ Create and return Calculator object to conduct the tax calculations. Parameters ---------- exact_calcs: boolean emulate_taxsim_2441_logic: boolean output_records: boolean Returns ------- calc: Calculator """ # create all-zeros dictionary and then list of all-zero dictionaries Records.read_var_info() zero_dict = {} for varname in Records.USABLE_READ_VARS: zero_dict[varname] = 0 dict_list = [zero_dict for _ in range(0, len(self._input))] # use dict_list to create a Pandas DataFrame and Records object recsdf = pd.DataFrame(dict_list, dtype='int64') recsdf['MARS'] = recsdf['MARS'].add(1) # because MARS==0 is illegal recs = Records(data=recsdf, exact_calculations=exact_calcs, gfactors=None, weights=None, start_year=self.policy.start_year) assert recs.array_length == len(self._input) # specify input for each tax filing unit in Records object lnum = 0 for idx in range(0, recs.array_length): lnum += 1 SimpleTaxIO._specify_input(recs, idx, self._input[lnum], emulate_taxsim_2441_logic) # optionally write Records.USABLE_READ_VARS content to file if output_records: recdf = pd.DataFrame() # pragma: no cover for varname in Records.USABLE_READ_VARS: # pragma: no cover vardata = getattr(recs, varname) # pragma: no cover recdf[varname] = vardata # pragma: no cover recdf.to_csv(re.sub('out-simtax', 'records', # pragma: no cover self._output_filename), float_format='%.2f', index=False) # create Calculator object containing all tax filing units return Calculator(policy=self.policy, records=recs, sync_years=False) @staticmethod def _specify_input(recs, idx, ivar, emulate_taxsim_2441_logic): """ Specifies recs values for index idx using ivar input variables. Parameters ---------- recs: Records Records object containing a row for each tax filing unit. idx: integer recs index of the tax filing unit whose input is in ivar. ivar: dictionary input variables for a single tax filing unit. emulate_taxsim_2441_logic: boolean Returns ------- nothing: void """ recs.RECID[idx] = ivar[1] # tax filing unit id recs.FLPDYR[idx] = ivar[2] # tax year # no use of ivar[3], state code (always equal to zero) if ivar[4] == 3: # head-of-household is 3 in SimpleTaxIO INPUT file mars_value = 4 # head-of-household is MARS=4 in Tax-Calculator num_taxpayers = 1 else: # if ivar[4] is 1=single or 2=married_filing_jointly mars_value = ivar[4] num_taxpayers = ivar[4] recs.MARS[idx] = mars_value # income tax filing status num_dependents = ivar[5] # total number of dependents num_eitc_qualified_kids = num_dependents # simplifying assumption recs.EIC[idx] = min(num_eitc_qualified_kids, 3) total_num_exemptions = num_taxpayers + num_dependents recs.XTOT[idx] = total_num_exemptions if ivar[6] < 3: # old coding of Internet-TAXSIM ivar[6] recs.age_head[idx] = 50 recs.age_spouse[idx] = 50 if ivar[6] >= 1: recs.age_head[idx] = 70 if ivar[6] >= 2: recs.age_spouse[idx] = 70 else: # new coding of Internet-TAXSIM ivar[6] since March 2016 recs.age_head[idx] = ivar[6] // 100 # integer division recs.age_spouse[idx] = ivar[6] % 100 # division remainder recs.e00200p[idx] = ivar[7] # wage+salary income of taxpayer recs.e00200s[idx] = ivar[8] # wage+salary income of spouse recs.e00200[idx] = ivar[7] + ivar[8] # combined wage+salary income recs.e00650[idx] = ivar[9] # qualified dividend income recs.e00600[idx] = ivar[9] # qual.div. included in ordinary dividends recs.e00300[idx] = ivar[10] # other property income (+/-) recs.e01700[idx] = ivar[11] # federally taxable pensions recs.e02400[idx] = ivar[12] # gross social security benefits recs.e00400[idx] = ivar[13] # federal tax-exempt interest # no use of ivar[14] because no state income tax calculations recs.e18500[idx] = ivar[15] # real-estate (property) taxes paid recs.e18400[idx] = ivar[16] # other AMT-preferred deductions recs.e32800[idx] = ivar[17] # child care expenses (Form 2441) # approximate number of Form 2441 qualified persons associated with # the child care expenses specified by ivar[17] (Note that the exact # number is the number of dependents under age 13, but that is not # an Internet-TAXSIM input variable; hence the need to approximate.) if emulate_taxsim_2441_logic: recs.f2441[idx] = num_dependents # all dependents of any age else: recs.f2441[idx] = ivar[19] # number dependents under age 17 recs.e02300[idx] = ivar[18] # unemployment compensation received recs.n24[idx] = ivar[19] # number dependents under age 17 recs.e19200[idx] = ivar[20] # AMT-nonpreferred deductions recs.p22250[idx] = ivar[21] # short-term capital gains (+/-) recs.p23250[idx] = ivar[22] # long-term capital gains (+/-)
def init(self, input_data, tax_year, reform, assump, growdiff_response, aging_input_data, exact_calculations): """ TaxCalcIO class post-constructor method that completes initialization. Parameters ---------- First four parameters are same as for TaxCalcIO constructor: input_data, tax_year, reform, assump. growdiff_response: Growdiff object or None growdiff_response Growdiff object is used only by the TaxCalcIO.growmodel_analysis method; must be None in all other cases. 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 parameter dictionaries from --reform and --assump files paramdict = Calculator.read_json_param_objects(reform, assump) # create Behavior object beh = Behavior() beh.update_behavior(paramdict['behavior']) self.behavior_has_any_response = beh.has_any_response() # create gdiff_baseline object gdiff_baseline = Growdiff() gdiff_baseline.update_growdiff(paramdict['growdiff_baseline']) # create Growfactors clp object that incorporates gdiff_baseline gfactors_clp = Growfactors() gdiff_baseline.apply_to(gfactors_clp) # specify gdiff_response object if growdiff_response is None: gdiff_response = Growdiff() gdiff_response.update_growdiff(paramdict['growdiff_response']) elif isinstance(growdiff_response, Growdiff): gdiff_response = growdiff_response else: gdiff_response = None msg = 'TaxCalcIO.more_init: growdiff_response is neither None ' msg += 'nor a Growdiff object' self.errmsg += 'ERROR: {}\n'.format(msg) if gdiff_response is not None: some_gdiff_response = gdiff_response.has_any_response() if self.behavior_has_any_response and some_gdiff_response: msg = 'ASSUMP file cannot specify any "behavior" when using ' msg += 'GrowModel or when ASSUMP file has "growdiff_response"' self.errmsg += 'ERROR: {}\n'.format(msg) # create Growfactors ref object that has both gdiff objects applied gfactors_ref = Growfactors() gdiff_baseline.apply_to(gfactors_ref) if gdiff_response is not None: gdiff_response.apply_to(gfactors_ref) # create Policy objects if self.specified_reform: pol = Policy(gfactors=gfactors_ref) try: pol.implement_reform(paramdict['policy']) self.errmsg += pol.reform_errors except ValueError as valerr_msg: self.errmsg += valerr_msg.__str__() else: pol = Policy(gfactors=gfactors_clp) clp = Policy(gfactors=gfactors_clp) # 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) clp.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_clp = Records.cps_constructor( gfactors=gfactors_clp, exact_calculations=exact_calculations) else: # if not cps_input_data recs = Records(data=input_data, gfactors=gfactors_ref, exact_calculations=exact_calculations) recs_clp = Records(data=input_data, gfactors=gfactors_clp, 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_clp = 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 con = Consumption() con.update_consumption(paramdict['consumption']) self.calc = Calculator(policy=pol, records=recs, verbose=True, consumption=con, behavior=beh, sync_years=aging_input_data) self.calc_clp = Calculator(policy=clp, records=recs_clp, verbose=False, consumption=con, sync_years=aging_input_data) # remember parameter dictionary for reform documentation self.param_dict = paramdict
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)
def init(self, input_data, tax_year, baseline, reform, assump, 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. 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 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) # 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__() # 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, sync_years=aging_input_data) self.calc_base = Calculator(policy=base, records=recs_base, verbose=False, consumption=con, sync_years=aging_input_data)
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) """
def __init__( self, input_data, tax_year, reform, assump, growdiff_response, # =None in static analysis aging_input_data, exact_calculations): """ TaxCalcIO class constructor. """ # pylint: disable=too-many-arguments # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements # check for existence of INPUT file if isinstance(input_data, six.string_types): # remove any leading directory path from INPUT filename fname = os.path.basename(input_data) # check if fname ends with ".csv" if fname.endswith('.csv'): inp = '{}-{}'.format(fname[:-4], str(tax_year)[2:]) else: msg = 'INPUT file named {} does not end in .csv' raise ValueError(msg.format(fname)) # check existence of INPUT file if not os.path.isfile(input_data): msg = 'INPUT file named {} could not be found' raise ValueError(msg.format(input_data)) elif isinstance(input_data, pd.DataFrame): inp = 'df-{}'.format(str(tax_year)[2:]) else: msg = 'INPUT is neither string nor Pandas DataFrame' raise ValueError(msg) # construct output_filename and delete old output file if it exists if reform is None: self._reform = False ref = '' elif isinstance(reform, six.string_types): self._reform = True # remove any leading directory path from REFORM filename fname = os.path.basename(reform) # check if fname ends with ".json" if fname.endswith('.json'): ref = '-{}'.format(fname[:-5]) else: msg = 'REFORM file named {} does not end in .json' raise ValueError(msg.format(fname)) else: msg = 'TaxCalcIO.ctor reform is neither None nor str' raise ValueError(msg) if assump is None: asm = '' elif isinstance(assump, six.string_types): # remove any leading directory path from ASSUMP filename fname = os.path.basename(assump) # check if fname ends with ".json" if fname.endswith('.json'): asm = '-{}'.format(fname[:-5]) else: msg = 'ASSUMP file named {} does not end in .json' raise ValueError(msg.format(fname)) else: msg = 'TaxCalcIO.ctor assump is neither None nor str' raise ValueError(msg) self._output_filename = '{}{}{}.csv'.format(inp, ref, asm) delete_file(self._output_filename) # get parameter dictionaries from --reform and --assump files param_dict = Calculator.read_json_param_files(reform, assump) # make sure no behavioral response is specified in --assump beh = Behavior() beh.update_behavior(param_dict['behavior']) if beh.has_any_response(): msg = '--assump ASSUMP cannot assume any "behavior"' raise ValueError(msg) # make sure no growdiff_response is specified in --assump gdiff_response = Growdiff() gdiff_response.update_growdiff(param_dict['growdiff_response']) if gdiff_response.has_any_response(): msg = '--assump ASSUMP cannot assume any "growdiff_response"' raise ValueError(msg) # create gdiff_baseline object gdiff_baseline = Growdiff() gdiff_baseline.update_growdiff(param_dict['growdiff_baseline']) # create Growfactors clp object that incorporates gdiff_baseline gfactors_clp = Growfactors() gdiff_baseline.apply_to(gfactors_clp) # specify gdiff_response object if growdiff_response is None: gdiff_response = Growdiff() elif isinstance(growdiff_response, Growdiff): gdiff_response = growdiff_response else: msg = 'TaxCalcIO.ctor growdiff_response is neither None nor {}' raise ValueError(msg.format('a Growdiff object')) # create Growfactors ref object that has both gdiff objects applied gfactors_ref = Growfactors() gdiff_baseline.apply_to(gfactors_ref) gdiff_response.apply_to(gfactors_ref) # create Policy object and implement reform if specified if self._reform: pol = Policy(gfactors=gfactors_ref) pol.implement_reform(param_dict['policy']) clp = Policy(gfactors=gfactors_clp) else: pol = Policy(gfactors=gfactors_clp) # check for valid tax_year value if tax_year < pol.start_year: msg = 'tax_year {} less than policy.start_year {}' raise ValueError(msg.format(tax_year, pol.start_year)) if tax_year > pol.end_year: msg = 'tax_year {} greater than policy.end_year {}' raise ValueError(msg.format(tax_year, pol.end_year)) # set policy to tax_year pol.set_year(tax_year) if self._reform: clp.set_year(tax_year) # read input file contents into Records object(s) if aging_input_data: if self._reform: recs = Records(data=input_data, gfactors=gfactors_ref, exact_calculations=exact_calculations) recs_clp = Records(data=input_data, gfactors=gfactors_clp, exact_calculations=exact_calculations) else: recs = Records(data=input_data, gfactors=gfactors_clp, exact_calculations=exact_calculations) else: # input_data are raw data that are not being aged recs = Records(data=input_data, exact_calculations=exact_calculations, gfactors=None, adjust_ratios=None, weights=None, start_year=tax_year) if self._reform: recs_clp = copy.deepcopy(recs) # create Calculator object(s) con = Consumption() con.update_consumption(param_dict['consumption']) self._calc = Calculator(policy=pol, records=recs, verbose=True, consumption=con, sync_years=aging_input_data) if self._reform: self._calc_clp = Calculator(policy=clp, records=recs_clp, verbose=False, consumption=con, sync_years=aging_input_data)