diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index b36268baa..32c496b66 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -6,7 +6,6 @@ # pylint --disable=locally-disabled taxcalcio.py import os -import sys import copy import six import pandas as pd @@ -52,16 +51,6 @@ class TaxCalcIO(object): specifies whether or not exact tax calculations are done without any smoothing of "stair-step" provisions in the tax law. - output_records: boolean - whether or not to write CSV-formatted file containing the values - of the Records.USABLE_READ_VARS variables in the tax_year. - - csv_dump: boolean - whether or not to write CSV-formatted output file containing the - values of the Records.USABLE_READ_VARS and Records.CALCULATED_VARS - variables. If true, the CSV-formatted output file replaces the - usual space-separated-values Internet-TAXSIM output file. - Raises ------ ValueError: @@ -77,8 +66,7 @@ class instance: TaxCalcIO """ def __init__(self, input_data, tax_year, reform, assump, - aging_input_data, exact_calculations, - output_records, csv_dump): + aging_input_data, exact_calculations): """ TaxCalcIO class constructor. """ @@ -86,8 +74,8 @@ def __init__(self, input_data, tax_year, reform, assump, # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals + # check for existence of INPUT file if isinstance(input_data, six.string_types): - self._using_input_file = True # remove any leading directory path from INPUT filename fname = os.path.basename(input_data) # check if fname ends with ".csv" @@ -95,9 +83,12 @@ def __init__(self, input_data, tax_year, reform, assump, 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): - self._using_input_file = False inp = 'df-{}'.format(str(tax_year)[2:]) else: msg = 'INPUT is neither string nor Pandas DataFrame' @@ -114,7 +105,8 @@ def __init__(self, input_data, tax_year, reform, assump, if fname.endswith('.json'): ref = '-{}'.format(fname[:-5]) else: - ref = '-{}'.format(fname) + 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) @@ -127,23 +119,14 @@ def __init__(self, input_data, tax_year, reform, assump, if fname.endswith('.json'): asm = '-{}'.format(fname[:-5]) else: - asm = '-{}'.format(fname) + 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) - if output_records: - self._output_filename = '{}.records{}{}'.format(inp, ref, asm) - elif csv_dump: - self._output_filename = '{}.csvdump{}{}'.format(inp, ref, asm) - else: - self._output_filename = '{}.out-inctax{}{}'.format(inp, ref, asm) + self._output_filename = '{}{}{}.csv'.format(inp, ref, asm) if os.path.isfile(self._output_filename): os.remove(self._output_filename) - # check for existence of INPUT file - if self._using_input_file: - if not os.path.isfile(input_data): - msg = 'INPUT file named {} could not be found' - raise ValueError(msg.format(input_data)) # get parameter dictionaries param_dict = Calculator.read_json_param_files(reform, assump) # create growdiff_baseline and growdiff_response objects @@ -176,7 +159,7 @@ def __init__(self, input_data, tax_year, reform, assump, if aging_input_data: recs = Records(data=input_data, exact_calculations=exact_calculations) - else: # input_data are raw data + else: # input_data are raw data that are not being aged recs = Records(data=input_data, exact_calculations=exact_calculations, gfactors=None, @@ -196,7 +179,7 @@ def __init__(self, input_data, tax_year, reform, assump, sync_years=aging_input_data) beh = Behavior() beh.update_behavior(param_dict['behavior']) - # Prevent both behavioral response and growdiff response + # prevent both behavioral response and growdiff response if beh.has_any_response() and growdiff_response.has_any_response(): msg = 'BOTH behavior AND growdiff_response HAVE RESPONSE' raise ValueError(msg) @@ -224,78 +207,24 @@ def output_filepath(self): dirpath = os.path.abspath(os.path.dirname(__file__)) return os.path.join(dirpath, self._output_filename) - def output_records(self, writing_output_file=False): - """ - Write CSV-formatted file containing the values of the - Records.USABLE_READ_VARS in the tax_year. The order of the - columns in this output file might not be the same as in the - input_data passed to TaxCalcIO constructor. - - Parameters - ---------- - writing_output_file: boolean - - Returns - ------- - Nothing - """ - recdf = pd.DataFrame() - for varname in Records.USABLE_READ_VARS: - vardata = getattr(self._calc.records, varname) - recdf[varname] = vardata - if self._using_input_file and writing_output_file: - recdf.to_csv(self._output_filename, - float_format='%.4f', index=False) - - def csv_dump(self, writing_output_file=False): - """ - Write CSV-formatted file containing the values of all the - Records.USABLE_READ_VARS variables and all the Records.CALCULATED_VARS - variables in the tax_year. - - Parameters - ---------- - writing_output_file: boolean - - Returns - ------- - Nothing - """ - recdf = pd.DataFrame() - for varname in Records.USABLE_READ_VARS | Records.CALCULATED_VARS: - vardata = getattr(self._calc.records, varname) - recdf[varname] = vardata - if self._using_input_file and writing_output_file: - recdf.to_csv(self._output_filename, - float_format='%.2f', index=False) - def calculate(self, writing_output_file=False, - exact_output=False, - output_weights=False, - output_mtr_wrt_fullcomp=False, - output_ceeu=False): + output_ceeu=False, + output_dump=False): """ Calculate taxes for all INPUT lines and write or return OUTPUT lines. - Output lines will be written to file if TaxCalcIO 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 - output_weights: boolean - whether or not to use s006 as an additional output variable. - - output_mtr_wrt_fullcomp: boolean - whether or not to calculate marginal tax rates in OUTPUT file with - respect to full compensation. - output_ceeu: boolean whether or not to calculate and write to stdout standard certainty-equivalent expected-utility statistics + output_dump: boolean + whether or not to include all input and calculated variables in + output + Returns ------- output_lines: string @@ -303,10 +232,11 @@ def calculate(self, writing_output_file=False, otherwise output_lines contain all OUTPUT lines """ # pylint: disable=too-many-arguments,too-many-locals - output = {} # dictionary indexed by Records index for filing unit - (mtr_ptax, mtr_itax, - _) = self._calc.mtr(wrt_full_compensation=output_mtr_wrt_fullcomp) - txt = None + # compute mtr wrt taxpayer earnings even if not needed for dump + # + (mtr_paytax, mtr_inctax, + _) = self._calc.mtr(wrt_full_compensation=False) + ceeu_results = None if self._reform: self._calc = Behavior.response(self._calc_clp, self._calc) if output_ceeu: @@ -314,112 +244,29 @@ def calculate(self, writing_output_file=False, self._calc_clp.calc_all() cedict = ce_aftertax_income(self._calc_clp, self._calc, require_no_agg_tax_change=False) - text = ('Aggregate {} Pre-Tax Expanded Income and ' - 'Tax Revenue ($billion)\n') - txt = text.format(cedict['year']) - txt += ' baseline reform difference\n' - fmt = '{} {:12.3f} {:10.3f} {:12.3f}\n' - txt += fmt.format('income', cedict['inc1'], cedict['inc2'], - cedict['inc2'] - cedict['inc1']) - alltaxdiff = cedict['tax2'] - cedict['tax1'] - txt += fmt.format('alltax', cedict['tax1'], cedict['tax2'], - alltaxdiff) - txt += ('Certainty Equivalent of Expected Utility of ' - 'After-Tax Expanded Income ($)\n') - txt += ('(assuming consumption equals ' - 'after-tax expanded income)\n') - txt += 'crra baseline reform pctdiff\n' - fmt = '{} {:17.2f} {:10.2f} {:11.2f}\n' - for crra, ceeu1, ceeu2 in zip(cedict['crra'], - cedict['ceeu1'], - cedict['ceeu2']): - txt += fmt.format(crra, ceeu1, ceeu2, - 100.0 * (ceeu2 - ceeu1) / ceeu1) - if abs(alltaxdiff) >= 0.0005: - txt += ('WARN: baseline and reform cannot be ' - 'sensibly compared\n') - text = ('because alltax difference is ' - '{:.3f} which is not zero\n') - txt += text.format(alltaxdiff) - txt += 'FIX: adjust _LST or other parameter to bracket\n' - txt += 'alltax difference equals zero and then interpolate' - else: - txt += ('NOTE: baseline and reform can be ' - 'sensibly compared\n') - txt += 'because alltax difference is essentially zero' - for idx in range(0, self._calc.records.dim): - ovar = TaxCalcIO.extract_output(self._calc.records, idx, - exact=exact_output, - extract_weight=output_weights) - ovar[7] = 100 * mtr_itax[idx] - ovar[9] = 100 * mtr_ptax[idx] - output[idx] = ovar - assert len(output) == self._calc.records.dim - # handle disposition of calculated output - olines = '' - if self._using_input_file and writing_output_file: - TaxCalcIO.write_output_file(output, self._output_filename) + ceeu_results = TaxCalcIO.ceeu_output(cedict) + # extract output + if output_dump: + outdf = self.dump_output(mtr_inctax, mtr_paytax) else: - for idx in range(0, len(output)): - olines += TaxCalcIO.construct_output_line(output[idx]) - if txt: - print(txt) # pylint: disable=superfluous-parens - return olines - - @staticmethod - def show_iovar_definitions(): - """ - Write definitions of INPUT and OUTPUT variables to stdout. - - Parameters - ---------- - none: void - - Returns - ------- - nothing: void - """ - ivd = ('**** TaxCalcIO INPUT variables determined by INPUT file,\n' - 'which is a CSV-formatted text file whose name ends in .csv\n' - 'and whose column names include the Records.MUST_READ_VARS.\n') - sys.stdout.write(ivd) - ovd = ('**** TaxCalcIO 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\n' - '[ 8] marginal state income tax rate [ALWAYS ZERO]\n' - '[ 9] marginal payroll tax rate\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) + outdf = self.standard_output() + assert len(outdf.index) == self._calc.records.dim + # handle disposition of output + output_lines = '' + if writing_output_file: + outdf.to_csv(self._output_filename, index=False, + float_format='%.2f') + else: + output_lines = outdf.to_string(index=False, + float_format='%.2f') + if ceeu_results: + print(ceeu_results) # pylint: disable=superfluous-parens + return output_lines + """ @staticmethod def construct_output_line(output_dict): - """ + Construct line of OUTPUT from a filing unit output_dict. Parameters @@ -430,7 +277,7 @@ def construct_output_line(output_dict): Returns ------- output_line: string - """ + outline = '' for vnum in range(1, len(output_dict) + 1): fnum = min(vnum, TaxCalcIO.OVAR_NUM) @@ -438,9 +285,11 @@ def construct_output_line(output_dict): outline += '\n' return outline + + @staticmethod def write_output_file(output, output_filename): - """ + Write all output to file with output_filename. Parameters @@ -452,134 +301,79 @@ def write_output_file(output, output_filename): Returns ------- nothing: void - """ + with open(output_filename, 'w') as output_file: for idx in range(0, len(output)): outline = TaxCalcIO.construct_output_line(output[idx]) output_file.write(outline) + """ - 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}'} + def standard_output(self): + """ + Extract standard output and return as pandas DataFrame. + """ + varlist = ['RECID', 'YEAR', 'WEIGHT', 'INCTAX', 'LSTAX', 'PAYTAX'] + odict = dict() + crecs = self._calc.records + odict['RECID'] = crecs.RECID # id for tax filing unit + odict['YEAR'] = crecs.FLPDYR # tax calculation year + odict['WEIGHT'] = crecs.s006 # sample weight + # pylint: disable=protected-access + odict['INCTAX'] = crecs._iitax # federal income taxes + odict['LSTAX'] = crecs.lumpsum_tax # lump-sum tax + odict['PAYTAX'] = crecs._payrolltax # payroll taxes (ee+er) + odf = pd.DataFrame(data=odict, columns=varlist) + return odf @staticmethod - def extract_output(crecs, idx, exact=False, extract_weight=False): + def ceeu_output(cedict): """ - Extracts tax output from crecs object for one tax filing unit. - - Parameters - ---------- - crecs: Records - Records object embedded in Calculator object. - - idx: integer - crecs object 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). + Extract --ceeu output and return as text string. """ - ovar = {} - ovar[1] = crecs.RECID[idx] # id for tax filing unit - ovar[2] = crecs.FLPDYR[idx] # year for which taxes are calculated - ovar[3] = 0 # state code is always zero - # pylint: disable=protected-access - ovar[4] = crecs._iitax[idx] # federal income tax liability - ovar[5] = 0.0 # no state income tax calculation - ovar[6] = crecs._payrolltax[idx] # payroll taxes (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] = crecs.c00100[idx] # federal AGI - ovar[11] = crecs.e02300[idx] # UI benefits in AGI - ovar[12] = crecs.c02500[idx] # OASDI benefits in AGI - ovar[13] = 0.0 # always set zero-bracket amount to zero - pre_phase_out_pe = crecs.pre_c04600[idx] - post_phase_out_pe = crecs.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] = crecs.c21040[idx] # itemized deduction that is phased out - # ovar[17] is zero for non-itemizer: - ovar[17] = crecs.c04470[idx] # post-phase-out itemized deduction - ovar[18] = crecs.c04800[idx] # federal regular taxable income - if exact: - ovar[19] = crecs._taxbc[idx] # regular tax on taxable income - else: # Internet-TAXSIM ovar[19] that ignores special qdiv+ltcg rates - ovar[19] = crecs.c05200[idx] # regular tax on taxable income - ovar[20] = 0.0 # always set exemption surtax to zero - ovar[21] = 0.0 # always set general tax credit to zero - ovar[22] = crecs.c07220[idx] # child tax credit (adjusted) - ovar[23] = crecs.c11070[idx] # extra child tax credit (refunded) - ovar[24] = crecs.c07180[idx] # child care credit - ovar[25] = crecs._eitc[idx] # federal EITC - ovar[26] = crecs.c62100[idx] # federal AMT taxable income - amt_liability = crecs.c09600[idx] # federal AMT liability - ovar[27] = amt_liability - # ovar[28] is federal income tax before credits; the Tax-Calculator - # crecs.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] = crecs.c05800[idx] - amt_liability - # add optional weight and debugging output to ovar dictionary - if extract_weight: - ovar[29] = crecs.s006[idx] # sample weight - num = TaxCalcIO.OVAR_NUM + 1 - else: - num = TaxCalcIO.OVAR_NUM - for dvar_name in TaxCalcIO.DVAR_NAMES: - num += 1 - dvar = getattr(crecs, dvar_name, None) - if dvar is None: - msg = 'debugging variable name "{}" not in calc.records object' - raise ValueError(msg.format(dvar_name)) + text = ('Aggregate {} Pre-Tax Expanded Income and ' + 'Tax Revenue ($billion)\n') + txt = text.format(cedict['year']) + txt += ' baseline reform difference\n' + fmt = '{} {:12.3f} {:10.3f} {:12.3f}\n' + txt += fmt.format('income', cedict['inc1'], cedict['inc2'], + cedict['inc2'] - cedict['inc1']) + alltaxdiff = cedict['tax2'] - cedict['tax1'] + txt += fmt.format('alltax', cedict['tax1'], cedict['tax2'], + alltaxdiff) + txt += ('Certainty Equivalent of Expected Utility of ' + 'After-Tax Expanded Income ($)\n') + txt += ('(assuming consumption equals ' + 'after-tax expanded income)\n') + txt += 'crra baseline reform pctdiff\n' + fmt = '{} {:17.2f} {:10.2f} {:11.2f}\n' + for crra, ceeu1, ceeu2 in zip(cedict['crra'], + cedict['ceeu1'], + cedict['ceeu2']): + txt += fmt.format(crra, ceeu1, ceeu2, + 100.0 * (ceeu2 - ceeu1) / ceeu1) + if abs(alltaxdiff) >= 0.0005: + txt += ('WARN: baseline and reform cannot be ' + 'sensibly compared\n') + text = ('because alltax difference is ' + '{:.3f} which is not zero\n') + txt += text.format(alltaxdiff) + txt += 'FIX: adjust _LST or other parameter to bracket\n' + txt += 'alltax difference equals zero and then interpolate' else: - ovar[num] = dvar[idx] - return ovar + txt += ('NOTE: baseline and reform can be ' + 'sensibly compared\n') + txt += 'because alltax difference is essentially zero' + return txt + + def dump_output(self, mtr_inctax, mtr_paytax): + """ + Extract --dump output and return as pandas DataFrame. + """ + odf = pd.DataFrame() + varset = Records.USABLE_READ_VARS | Records.CALCULATED_VARS + for varname in varset: + vardata = getattr(self._calc.records, varname) + odf[varname] = vardata + odf['mtr_inctax'] = mtr_inctax + odf['mtr_paytax'] = mtr_paytax + return odf diff --git a/taxcalc/tests/test_functions.py b/taxcalc/tests/test_functions.py index 2e150200a..2215c54f0 100644 --- a/taxcalc/tests/test_functions.py +++ b/taxcalc/tests/test_functions.py @@ -8,15 +8,8 @@ import os import re import ast -from io import StringIO -import tempfile import six -import pytest -import pandas as pd -from taxcalc import TaxCalcIO, Records # pylint: disable=import-error - - -# for fixture args, pylint: disable=redefined-outer-name +from taxcalc import Records # pylint: disable=import-error class GetFuncDefs(ast.NodeVisitor): @@ -162,134 +155,3 @@ def test_function_args_usage(tests_path): msg += 'FUNCTION,ARGUMENT= {} {}\n'.format(fname, arg) if found_error: raise ValueError(msg) - - -@pytest.yield_fixture -def reformfile1(): - """ - specify JSON text for reform - """ - txt = """ - { - "policy": { - "_SS_Earnings_c": {"2015": [100000]}, - "_FICA_ss_trt": {"2015": [0.124]}, - "_FICA_mc_trt": {"2015": [0.029]} - } - } - """ - rfile = tempfile.NamedTemporaryFile(mode='a', delete=False) - rfile.write(txt + '\n') - rfile.close() - # Must close and then yield for Windows platform - yield rfile - os.remove(rfile.name) - - -def test_1(reformfile1): - """ - Test calculation of AGI adjustment for half of Self-Employment Tax, - which is the payroll tax on self-employment income. - """ - agi_ovar_num = 10 - funit = ( - u'RECID,MARS,e00200,e00200p,e00900,e00900p,e00900s\n' - u'1, 2, 200000, 200000,200000, 0, 200000\n' - u'2, 1, 100000, 100000,200000, 200000, 0\n' - ) - # ==== Filing unit with RECID=1: - # The expected AGI for this filing unit is $400,000 minus half of - # the spouse's Self-Employment Tax (SET), which represents the - # "employer share" of the SET. The spouse pays OASDI SET on only - # $100,000 of self-employment income, which implies a tax of - # $12,400. The spouse also pays HI SET on 0.9235 * 200000 * 0.029, - # which implies a tax of $5,356.30. So, the spouse's total SET is - # the sum of the two, which equals $17,756.30. - # The SET AGI adjustment is one-half of that amount, which is $8,878.15. - # So, filing unit's AGI is $400,000 less this, which is $391,121.85. - expected_agi_1 = '391121.85' - # ==== Filing unit with RECID=2: - # The expected AGI for this filing unit is $300,000 minus half of - # the individual's Self-Employment Tax (SET), which represents the - # "employer share" of the SET. The individual pays no OASDI SET - # because wage and salary income was already at the MTE. So, the - # only SET the individual pays is for HI, which is a tax equal to - # 0.9235 * 200000 * 0.029, or $5,356.30. One half of that amount - # is $2,678.15, which implies the AGI is $297,321.85. - expected_agi_2 = '297321.85' - input_dataframe = pd.read_csv(StringIO(funit)) - tcio = TaxCalcIO(input_data=input_dataframe, - tax_year=2015, - reform=reformfile1.name, - assump=None, - aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) - output = tcio.calculate() - output_lines_list = output.split('\n') - output_vars_list = output_lines_list[0].split() - agi = output_vars_list[agi_ovar_num - 1] - assert agi == expected_agi_1 - output_vars_list = output_lines_list[1].split() - agi = output_vars_list[agi_ovar_num - 1] - assert agi == expected_agi_2 - - -@pytest.yield_fixture -def reformfile2(): - """ - specify JSON text for implausible reform to make hand calcs easier - """ - txt = """ - { - "policy": { - "_ACTC_Income_thd": {"2015": [35000]}, - "_SS_Earnings_c": {"2015": [53000]} - } - } - """ - rfile = tempfile.NamedTemporaryFile(mode='a', delete=False) - rfile.write(txt + '\n') - rfile.close() - # Must close and then yield for Windows platform - yield rfile - os.remove(rfile.name) - - -def test_2(reformfile2): - """ - Test calculation of Additional Child Tax Credit (CTC) when at least one - of taxpayer and spouse have wage-and-salary income above the FICA maximum - taxable earnings. - """ - ctc_ovar_num = 22 - actc_ovar_num = 23 - funit = ( - u'RECID,MARS,XTOT,n24,e00200,e00200p\n' - u'1, 2, 9, 7, 60000, 60000\n' - ) - # The maximum CTC in the above situation is $7,000, but only $1,140 can - # be paid as a nonrefundable CTC. Because the family has 3+ kids, they - # can compute their refundable CTC amount in a way that uses the - # employee share of FICA taxes paid on wage-and-salary income. In this - # case, the employee share is the sum of HI FICA (0.0145*60000) $870 and - # OASDI FICA (0.062*53000) $3,286, which is $4,156, which is larger than - # the $3,700 available to smaller families. - expected_ctc = '1140.00' - expected_actc = '4156.00' - input_dataframe = pd.read_csv(StringIO(funit)) - tcio = TaxCalcIO(input_data=input_dataframe, - tax_year=2015, - reform=reformfile2.name, - assump=None, - aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) - output = tcio.calculate() - output_vars_list = output.split() - ctc = output_vars_list[ctc_ovar_num - 1] - actc = output_vars_list[actc_ovar_num - 1] - assert ctc == expected_ctc - assert actc == expected_actc diff --git a/taxcalc/tests/test_taxcalcio.py b/taxcalc/tests/test_taxcalcio.py index 693869ab4..c4806dc9a 100644 --- a/taxcalc/tests/test_taxcalcio.py +++ b/taxcalc/tests/test_taxcalcio.py @@ -23,15 +23,12 @@ ) -EXPECTED_OUTPUT = ( # from using RAWINPUTFILE_CONTENTS as input - '1. 2021 0 0.00 0.00 0.00 -7.65 0.00 15.30 0.00 0.00 0.00 0.00 0.00 ' - '0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00\n' - '2. 2021 0 0.00 0.00 0.00 -7.65 0.00 15.30 0.00 0.00 0.00 0.00 0.00 ' - '0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00\n' - '3. 2021 0 0.00 0.00 0.00 -7.65 0.00 15.30 0.00 0.00 0.00 0.00 0.00 ' - '0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00\n' - '4. 2021 0 0.00 0.00 0.00 0.00 0.00 15.30 0.00 0.00 0.00 0.00 0.00 ' - '0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00\n' +EXPECTED_TO_STRING_OUTPUT = ( # from using RAWINPUTFILE_CONTENTS as input + 'RECID YEAR WEIGHT INCTAX LSTAX PAYTAX\n' + ' 1 2021 0.0 0.0 0.0 0.0\n' + ' 2 2021 0.0 0.0 0.0 0.0\n' + ' 3 2021 0.0 0.0 0.0 0.0\n' + ' 4 2021 0.0 0.0 0.0 0.0' # no trailing EOL character ) @@ -62,87 +59,83 @@ def test_incorrect_creation_1(input_data, exact): Ensure a ValueError is raised when created with invalid data pointers """ with pytest.raises(ValueError): - TaxCalcIO( - input_data=input_data, - tax_year=2013, - reform=None, - assump=None, - aging_input_data=False, - exact_calculations=exact, - output_records=False, - csv_dump=False - ) + TaxCalcIO(input_data=input_data, + tax_year=2013, + reform=None, + assump=None, + aging_input_data=False, + exact_calculations=exact) + + +@pytest.yield_fixture +def reformfile0(): + """ + specify JSON text for reform + """ + txt = """ + { + "policy": { + "_SS_Earnings_c": {"2016": [300000], + "2018": [500000], + "2020": [700000]} + } + } + """ + rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False) + rfile.write(txt + '\n') + rfile.close() + # Must close and then yield for Windows platform + yield rfile + if os.path.isfile(rfile.name): + try: + os.remove(rfile.name) + except OSError: + pass # sometimes we can't remove a generated temporary file # for fixture args, pylint: disable=redefined-outer-name -@pytest.mark.parametrize("year, reform, assump", [ +@pytest.mark.parametrize("year, ref, asm", [ (2013, list(), None), (2013, None, list()), (2001, None, None), (2099, None, None), + (2020, 'no-dot-json-reformfile', None), + (2020, 'reformfile0', 'no-dot-json-assumpfile'), ]) -def test_incorrect_creation_2(rawinputfile, year, reform, assump): +def test_incorrect_creation_2(rawinputfile, reformfile0, year, ref, asm): """ Ensure a ValueError is raised when created with invalid parameters """ + if ref == 'reformfile0': + reform = reformfile0.name + else: + reform = ref with pytest.raises(ValueError): TaxCalcIO( input_data=rawinputfile.name, tax_year=year, reform=reform, - assump=assump, + assump=asm, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False - ) + exact_calculations=False) def test_creation_with_aging(rawinputfile): """ Test TaxCalcIO instantiation with no policy reform and with aging. """ - TaxCalcIO.show_iovar_definitions() taxyear = 2021 tcio = TaxCalcIO(input_data=rawinputfile.name, tax_year=taxyear, reform=None, assump=None, aging_input_data=True, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) assert tcio.tax_year() == taxyear -@pytest.yield_fixture -def reformfile0(): - """ - specify JSON text for reform - """ - txt = """ - { - "policy": { - "_SS_Earnings_c": {"2016": [300000], - "2018": [500000], - "2020": [700000]} - } - } - """ - rfile = tempfile.NamedTemporaryFile(mode='a', delete=False) - rfile.write(txt + '\n') - rfile.close() - # Must close and then yield for Windows platform - yield rfile - if os.path.isfile(rfile.name): - try: - os.remove(rfile.name) - except OSError: - pass # sometimes we can't remove a generated temporary file - - def test_2(rawinputfile, reformfile0): """ Test TaxCalcIO calculate method with no output writing and no aging. @@ -153,11 +146,9 @@ def test_2(rawinputfile, reformfile0): reform=reformfile0.name, assump=None, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) output = tcio.calculate() - assert output == EXPECTED_OUTPUT + assert output == EXPECTED_TO_STRING_OUTPUT REFORM_CONTENTS = """ @@ -223,7 +214,7 @@ def reformfile2(): """ Temporary reform file without .json extension. """ - rfile = tempfile.NamedTemporaryFile(mode='a', delete=False) + rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False) rfile.write(REFORM_CONTENTS) rfile.close() # must close and then yield for Windows platform @@ -273,23 +264,6 @@ def assumpfile1(): pass # sometimes we can't remove a generated temporary file -@pytest.yield_fixture -def assumpfile2(): - """ - Temporary assumption file without .json extension. - """ - afile = tempfile.NamedTemporaryFile(mode='a', delete=False) - afile.write(ASSUMP_CONTENTS) - afile.close() - # must close and then yield for Windows platform - yield afile - if os.path.isfile(afile.name): - try: - os.remove(afile.name) - except OSError: - pass # sometimes we can't remove a generated temporary file - - def test_3(rawinputfile, reformfile1, assumpfile1): """ Test TaxCalcIO calculate method with output writing but no aging, @@ -301,29 +275,9 @@ def test_3(rawinputfile, reformfile1, assumpfile1): reform=reformfile1.name, assump=assumpfile1.name, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) outfilepath = tcio.output_filepath() # try file writing - try: - tcio.output_records(writing_output_file=True) - except: # pylint: disable=bare-except - if os.path.isfile(outfilepath): - try: - os.remove(outfilepath) - except OSError: - pass # sometimes we can't remove a generated temporary file - assert 'TaxCalcIO.output_records()_ok' == 'no' - try: - tcio.csv_dump(writing_output_file=True) - except: # pylint: disable=bare-except - if os.path.isfile(outfilepath): - try: - os.remove(outfilepath) - except OSError: - pass # sometimes we can't remove a generated temporary file - assert 'TaxCalcIO.csv_dump()_ok' == 'no' try: output = tcio.calculate(writing_output_file=True) except: # pylint: disable=bare-except @@ -333,17 +287,17 @@ def test_3(rawinputfile, reformfile1, assumpfile1): except OSError: pass # sometimes we can't remove a generated temporary file assert 'TaxCalcIO.calculate()_ok' == 'no' - # if all tries were successful, try to remove the output file + # if the try was successful, try to remove the output file if os.path.isfile(outfilepath): try: os.remove(outfilepath) except OSError: pass # sometimes we can't remove a generated temporary file # check that output is empty string (because output was written to file) - assert output == "" + assert output == '' -def test_4(reformfile2, assumpfile2): +def test_4(reformfile2, assumpfile1): """ Test TaxCalcIO calculate method with no output writing and no aging, using DataFrame for TaxCalcIO constructor input_data. @@ -354,37 +308,20 @@ def test_4(reformfile2, assumpfile2): tcio = TaxCalcIO(input_data=input_dataframe, tax_year=taxyear, reform=reformfile2.name, - assump=assumpfile2.name, + assump=assumpfile1.name, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) output = tcio.calculate() - assert output == EXPECTED_OUTPUT + assert output == EXPECTED_TO_STRING_OUTPUT -def test_5(rawinputfile, reformfile1): - """ - Test TaxCalcIO calculate method with no output writing and no aging and - no reform, using the output_records option. - """ - taxyear = 2020 - tcio = TaxCalcIO(input_data=rawinputfile.name, - tax_year=taxyear, - reform=reformfile1.name, - assump=None, - aging_input_data=False, - exact_calculations=False, - output_records=True, - csv_dump=False) - tcio.output_records(writing_output_file=False) - assert tcio.tax_year() == taxyear +# remove test_5 because there is no longer a TaxCalcIO.output_records() method def test_6(rawinputfile): """ Test TaxCalcIO calculate method with no output writing and no aging and - no reform, using the csv_dump option. + no reform, using the --dump option. """ taxyear = 2021 tcio = TaxCalcIO(input_data=rawinputfile.name, @@ -392,10 +329,9 @@ def test_6(rawinputfile): reform=None, assump=None, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=True) - tcio.csv_dump(writing_output_file=False) + exact_calculations=False) + output = tcio.calculate(writing_output_file=False, output_dump=True) + assert len(output) > 5000 assert tcio.tax_year() == taxyear @@ -413,7 +349,7 @@ def lumpsumreformfile(): """ Temporary reform file without .json extension. """ - rfile = tempfile.NamedTemporaryFile(mode='a', delete=False) + rfile = tempfile.NamedTemporaryFile(suffix='.json', mode='a', delete=False) rfile.write(LUMPSUM_REFORM_CONTENTS) rfile.close() # must close and then yield for Windows platform @@ -438,9 +374,7 @@ def test_7(reformfile1, lumpsumreformfile): reform=reformfile1.name, assump=None, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) output = tcio.calculate(writing_output_file=False, output_ceeu=True) assert tcio.tax_year() == taxyear assert len(output) > 0 @@ -450,9 +384,7 @@ def test_7(reformfile1, lumpsumreformfile): reform=lumpsumreformfile.name, assump=None, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) + exact_calculations=False) output = tcio.calculate(writing_output_file=False, output_ceeu=True) assert tcio.tax_year() == taxyear assert len(output) > 0 @@ -504,42 +436,4 @@ def test_9(reformfile2, assumpfile3): reform=reformfile2.name, assump=assumpfile3.name, aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) - - -INPUT_CONTENTS = ( - u'RECID,MARS,e00600 \n' - u'1, 1, 95000 \n' - u'2, 2, 15000 \n' - u'3, 3, 40000 \n' - u'4, 2, 15000 \n' -) - - -def test_10(): - """ - Test SimpleTaxIO instantiation with no policy reform. - """ - TaxCalcIO.show_iovar_definitions() - input_stream = StringIO(INPUT_CONTENTS) - input_dataframe = pd.read_csv(input_stream) - taxyear = 2022 - tcio = TaxCalcIO(input_data=input_dataframe, - tax_year=taxyear, - reform=None, - assump=None, - aging_input_data=False, - exact_calculations=False, - output_records=False, - csv_dump=False) - # test extracting of weight and debugging variables - crecs = tcio._calc.records # pylint: disable=protected-access - TaxCalcIO.DVAR_NAMES = ['f2441'] - ovar = TaxCalcIO.extract_output(crecs, # pylint: disable=unused-variable - 0, exact=True, extract_weight=True) - TaxCalcIO.DVAR_NAMES = ['badvar'] - with pytest.raises(ValueError): - ovar = TaxCalcIO.extract_output(crecs, 0) - TaxCalcIO.DVAR_NAMES = [] + exact_calculations=False) diff --git a/taxcalc/validation/taxsim/test b/taxcalc/validation/taxsim/test index 2da562f9f..d4704523c 100755 --- a/taxcalc/validation/taxsim/test +++ b/taxcalc/validation/taxsim/test @@ -49,6 +49,10 @@ unzip -oq output-taxsim.zip $LYY.in.out-taxsim$SUFFIX # Compare simtax and Internet-TAXSIM OUTPUT tclsh ../taxdiffs.tcl $OVAR4 $LYY.in.out-simtax$SUFFIX \ $LYY.in.out-taxsim$SUFFIX > $LYY$SUFFIX.taxdiffs +RC=$? +if [ $RC -ne 0 ]; then + exit $RC +fi # Check for difference between actual .taxdiffs and expected .taxdiffs files DIR="taxsim" RC=0 diff --git a/tc.py b/tc.py index 1917461e4..11617f2d5 100644 --- a/tc.py +++ b/tc.py @@ -19,82 +19,37 @@ def main(): prog='python tc.py', description=('Writes to a file the federal income and payroll tax ' 'OUTPUT for each filing unit specified in the INPUT ' - 'file, with the OUTPUT computed from the INPUT for ' - 'the TAXYEAR using the Tax-Calculator. ' - 'The INPUT file is a CSV-formatted file that contains ' - 'variable names that include the Records.MUST_READ_VARS ' - 'set plus other variables. The OUTPUT file is in ' - 'Internet-TAXSIM format. The OUTPUT file name is the ' - 'INPUT file name (excluding directory path prefix and ' - '.csv suffix) followed by a string equal to "-YY" ' - '(where the YY is the last two digits in the TAXYEAR) ' - 'and all that is followed by a trailing string. The ' - 'trailing string is ".out-inctax" if no --reform ' - 'option is specified; otherwise the trailing string is ' - '".out-inctax-REFORM" (excluding any ".json" ending to ' - 'the REFORM file name) if no --assump option is ' - 'specified or ".out-inctax-REFORM-ASSUMP" (excluding ' - 'any .json ending to the ASSUMP file name) if an ' - '--assump option is specified. The OUTPUT file ' - 'contains the first 28 Internet-TAXSIM output ' - 'variables. Use --iohelp flag for more information. ' - 'For details on the Internet-TAXSIM version 9.3 ' - 'OUTPUT format, go to ' - '.')) - parser.add_argument('--iohelp', - help=('optional flag to show INPUT and OUTPUT ' - 'variable definitions and exit.'), - default=False, - action="store_true") + 'file, with the OUTPUT computed from the INPUT for the ' + 'TAXYEAR using Tax-Calculator. The OUTPUT file is a ' + 'CSV-formatted file that contains tax information for ' + 'each INPUT filing unit.')) + parser.add_argument('INPUT', nargs='?', default='', + help=('INPUT is name of CSV-formatted file that ' + 'contains for each filing unit variables used ' + 'to compute taxes for TAXYEAR.')) + parser.add_argument('TAXYEAR', nargs='?', default=0, + help=('TAXYEAR is calendar year for which taxes ' + 'are computed.'), + type=int) parser.add_argument('--reform', - help=('REFORM is name of optional file that contains ' - 'reform "policy" parameters; the REFORM file ' - 'is specified using JSON that may include ' - '//-comments. No --reform implies use of ' - 'current-law policy.'), + help=('REFORM is name of optional JSON reform file. ' + 'No --reform implies use of current-law ' + 'policy.'), default=None) parser.add_argument('--assump', - help=('ASSUMP is name of optional file that contains ' - 'economic assumption parameters ("consumption" ' - 'and "behavior" and "growdiff_baseline" and ' - '"growdiff_response"); the ASSUMP file is ' - 'specified using JSON that may include ' - '//-comments. No --assump implies use ' - 'of static analysis assumptions. Note that ' - 'use of the --assump option requires use of ' - 'the --reform option (although the specified ' - 'reform could be empty, meaning it could be ' - 'current-law policy).'), + help=('ASSUMP is name of optional JSON economic ' + 'assumption file. No --assump implies use of ' + 'static analysis assumptions.'), default=None) - parser.add_argument('--noaging', - help=('optional flag implies INPUT data are ' - 'considered raw data that are not aged in ' - 'any way. No --noaging flag implies that the ' - 'PUF-related or CPS-related extrapolation (or ' - 'blowup) logic, sample reweighting logic and ' - 'income ajustment logic, will be used to age ' - 'the INPUT data from the INPUT data year to ' - 'TAXYEAR.'), - default=False, - action="store_true") parser.add_argument('--exact', - help=('optional flag to suppress smoothing in income ' - 'tax calculations that eliminate marginal-tax-' - 'rate-complicating "stair-steps". The default ' - 'is to smooth, and therefore, not to do the ' - ' exact calculations called for in the tax ' - 'law.'), + help=('optional flag that suppresses the smoothing of ' + '"stair-step" provisions in the tax law that ' + 'complicate marginal-tax-rate calculations.'), default=False, action="store_true") - parser.add_argument('--fullcomp', - help=('optional flag that causes OUTPUT to have ' - 'marginal tax rates (MTRs) calculated with ' - 'respect to full compensation (but any ' - 'behavioral-response calculations always use ' - 'MTRs that are calculated with respect to full ' - 'compensation). No --fullcomp flag implies ' - 'MTRs reported in OUTPUT are not calculated ' - 'with respect to full compensation.'), + parser.add_argument('--graph', + help=('optional flag that causes HTML graphs to be ' + 'generated.'), default=False, action="store_true") parser.add_argument('--ceeu', @@ -102,94 +57,63 @@ def main(): 'statistics, including certainty-equivalent ' 'expected-utility of after-tax income values ' 'for different constant-relative-risk-aversion ' - 'parameter values, to be written to stdout. ' - 'No --ceeu flag implies nothing is written ' - 'to stdout. Note that --reform option must ' - 'be specified and aggregate combined taxes ' - 'under that reform must be same as under ' - 'current-law policy for this option to work.'), - default=False, - action="store_true") - output = parser.add_mutually_exclusive_group(required=False) - output.add_argument('--records', - help=('optional flag that causes the OUTPUT file to ' - 'be a CSV-formatted file containing for each ' - 'INPUT filing unit the TAXYEAR values of each ' - 'variable in the Records.USABLE_READ_VARS set. ' - 'If the --records option is specified, the ' - 'OUTPUT file name will be the same as if the ' - 'option was not specified, except that the ' - '".out-inctax" part is replaced by ' - '".records".'), + 'parameter values, to be written to screen.'), default=False, action="store_true") - output.add_argument('--csvdump', - help=('optional flag that causes the OUTPUT file to ' - 'be a CSV-formatted file containing for each ' - 'INPUT filing unit the TAXYEAR values of each ' - 'variable in the Records.USABLE_READ_VARS set ' - 'and in the Records.CALCULATED_VARS set. ' - 'If the --csvdump option is specified, the ' - 'OUTPUT file name will be the same as if the ' - 'option was not specified, except that the ' - '".out-inctax" part is replaced by ' - '".csvdump".'), + parser.add_argument('--dump', + help=('optional flag that causes OUTPUT to contain ' + 'all INPUT variables (possibly aged to TAXYEAR) ' + 'and all calculated tax variables, where the ' + 'variables are named using their internal ' + 'Tax-Calculator names.'), default=False, action="store_true") - parser.add_argument('INPUT', nargs='?', default='', - help=('INPUT is name of required CSV file that ' - 'contains a subset of variables included in ' - 'the Records.USABLE_READ_VARS set. ' - 'INPUT must end in ".csv".')) - parser.add_argument('TAXYEAR', nargs='?', default=0, - help=('TAXYEAR is calendar year for which federal ' - 'income taxes are computed (e.g., 2013).'), - type=int) args = parser.parse_args() - # optionally show INPUT and OUTPUT variable definitions and exit - if args.iohelp: - TaxCalcIO.show_iovar_definitions() - return 0 # check INPUT file name if args.INPUT == '': sys.stderr.write('ERROR: must specify INPUT file name;\n') - sys.stderr.write('USAGE: python inctax.py --help\n') + sys.stderr.write('USAGE: python tc.py --help\n') return 1 # check TAXYEAR value if args.TAXYEAR == 0: sys.stderr.write('ERROR: must specify TAXYEAR >= 2013;\n') - sys.stderr.write('USAGE: python inctax.py --help\n') + sys.stderr.write('USAGE: python tc.py --help\n') return 1 - # check consistency of REFORM and ASSUMP options + # check consistency of --reform and --assump options if args.assump and not args.reform: - sys.stderr.write('ERROR: cannot specify ASSUMP without REFORM\n') - sys.stderr.write('USAGE: python inctax.py --help\n') + sys.stderr.write('ERROR: cannot use --assump without --reform\n') + sys.stderr.write('USAGE: python tc.py --help\n') + return 1 + # check consistency of --reform and --graph options + if args.graph and not args.reform: + sys.stderr.write('ERROR: cannot specify --graph without --reform\n') + sys.stderr.write('USAGE: python tc.py --help\n') return 1 - # instantiate IncometaxIO object and do federal inc/pay tax calculations - aging_and_weights = args.noaging is False - inctax = TaxCalcIO(input_data=args.INPUT, - tax_year=args.TAXYEAR, - reform=args.reform, - assump=args.assump, - aging_input_data=aging_and_weights, - exact_calculations=args.exact, - output_records=args.records, - csv_dump=args.csvdump) - if args.records: - inctax.output_records(writing_output_file=True) - elif args.csvdump: - inctax.calculate(writing_output_file=False, - exact_output=args.exact, - output_weights=aging_and_weights, - output_mtr_wrt_fullcomp=args.fullcomp, - output_ceeu=args.ceeu) - inctax.csv_dump(writing_output_file=True) + # check consistency of --reform and --ceeu options + if args.ceeu and not args.reform: + sys.stderr.write('ERROR: cannot specify --ceeu without --reform\n') + sys.stderr.write('USAGE: python tc.py --help\n') + return 1 + + if args.graph: + sys.stderr.write('ERROR: --graph option not yet implemented\n') + sys.stderr.write('USAGE: python tc.py --help\n') + return 1 + + # instantiate TaxCalcIO object and do federal tax calculations + if args.INPUT.endswith('puf.csv') or args.INPUT.endswith('cps.csv'): + aging_input = True else: - inctax.calculate(writing_output_file=True, - exact_output=args.exact, - output_weights=aging_and_weights, - output_mtr_wrt_fullcomp=args.fullcomp, - output_ceeu=args.ceeu) + aging_input = False + tcio = TaxCalcIO(input_data=args.INPUT, + tax_year=args.TAXYEAR, + reform=args.reform, + assump=args.assump, + aging_input_data=aging_input, + exact_calculations=args.exact) + tcio.calculate(writing_output_file=True, + output_ceeu=args.ceeu, + output_dump=args.dump) # return no-error exit code return 0 # end of main function code