From 4c2dae4bf637a6e7d4e6cbb437b7f434b0b815cd Mon Sep 17 00:00:00 2001 From: martinholmer Date: Sun, 23 Apr 2017 21:13:34 -0400 Subject: [PATCH 1/4] Add --sqldb option to tc CLI --- taxcalc/cli/tc.py | 10 +++++-- taxcalc/taxcalcio.py | 26 ++++++++++++++++--- taxcalc/tests/test_taxcalcio.py | 46 ++++++++++++++++++++++++++++----- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/taxcalc/cli/tc.py b/taxcalc/cli/tc.py index 9172cdf26..ed8840a0d 100644 --- a/taxcalc/cli/tc.py +++ b/taxcalc/cli/tc.py @@ -24,7 +24,7 @@ def main(): usage_str = 'tc INPUT TAXYEAR {}{}{}'.format( '[--reform REFORM] [--assump ASSUMP]\n', ' ', - '[--exact] [--tables] [--graphs] [--ceeu] [--dump] [--test]') + '[--exact] [--tables] [--graphs] [--ceeu] [--dump] [-sqldb] [--test]') parser = argparse.ArgumentParser( prog='', usage=usage_str, @@ -89,6 +89,11 @@ def main(): 'output.'), default=False, action="store_true") + parser.add_argument('--sqldb', + help=('optional flag that writes SQLite database with ' + 'dump table containing same output as --dump.'), + default=False, + action="store_true") parser.add_argument('--test', help=('optional flag that conducts installation ' 'test.'), @@ -124,7 +129,8 @@ def main(): output_tables=args.tables, output_graphs=args.graphs, output_ceeu=args.ceeu, - output_dump=args.dump) + output_dump=args.dump, + output_sqldb=args.sqldb) # compare test output with expected test output if --test option specified if args.test: compare_test_output_files() diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index 6a3e9daad..813e9d876 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -7,6 +7,7 @@ import os import copy +import sqlite3 import six import numpy as np import pandas as pd @@ -264,7 +265,8 @@ def analyze(self, writing_output_file=False, output_tables=False, output_graphs=False, output_ceeu=False, - output_dump=False): + output_dump=False, + output_sqldb=False): """ Conduct tax analysis. @@ -289,13 +291,17 @@ def analyze(self, writing_output_file=False, whether or not to replace standard output with all input and calculated variables using their Tax-Calculator names + output_sqldb: boolean + whether or not to write SQLite3 database with dump table + containing same output as written by output_dump to a csv file + Returns ------- Nothing """ # pylint: disable=too-many-arguments,too-many-branches calc_clp_calculated = False - if output_dump: + if output_dump or output_sqldb: (mtr_paytax, mtr_inctax, _) = self.calc.mtr(wrt_full_compensation=False) else: # do not need marginal tax rates @@ -325,6 +331,9 @@ def analyze(self, writing_output_file=False, # extract output if writing_output_file if writing_output_file: self.write_output_file(output_dump, mtr_paytax, mtr_inctax) + # optionally write --sqldb output to SQLite3 database + if output_sqldb: + self.write_sqldb_file(mtr_paytax, mtr_inctax) # optionally write --tables output to text file if output_tables: if not calc_clp_calculated: @@ -352,6 +361,17 @@ def write_output_file(self, output_dump, mtr_paytax, mtr_inctax): assert len(outdf.index) == self.calc.records.dim outdf.to_csv(self._output_filename, index=False, float_format='%.2f') + def write_sqldb_file(self, mtr_paytax, mtr_inctax): + """ + Write dump output to SQLite3 database table dump. + """ + outdf = self.dump_output(mtr_inctax, mtr_paytax) + assert len(outdf.index) == self.calc.records.dim + dbfilename = '{}.db'.format(self._output_filename[:-4]) + dbcon = sqlite3.connect(dbfilename) + outdf.to_sql('dump', dbcon, if_exists='replace', index=False) + dbcon.close() + def write_tables_file(self): """ Write tables to text file. @@ -531,7 +551,7 @@ def ceeu_output(cedict): def dump_output(self, mtr_inctax, mtr_paytax): """ - Extract --dump output and return as pandas DataFrame. + Extract --dump output and return it as pandas DataFrame. """ odf = pd.DataFrame() varset = Records.USABLE_READ_VARS | Records.CALCULATED_VARS diff --git a/taxcalc/tests/test_taxcalcio.py b/taxcalc/tests/test_taxcalcio.py index 823a7a05b..ed28eb649 100644 --- a/taxcalc/tests/test_taxcalcio.py +++ b/taxcalc/tests/test_taxcalcio.py @@ -266,7 +266,7 @@ def test_output_otions(rawinputfile, reformfile1, assumpfile1): os.remove(outfilepath) except OSError: pass # sometimes we can't remove a generated temporary file - assert 'TaxCalcIO.calculate(ceeu)_ok' == 'no' + assert 'TaxCalcIO.analyze(ceeu)_ok' == 'no' # --dump output try: tcio.analyze(writing_output_file=True, output_dump=True) @@ -276,13 +276,45 @@ def test_output_otions(rawinputfile, reformfile1, assumpfile1): os.remove(outfilepath) except OSError: pass # sometimes we can't remove a generated temporary file - assert 'TaxCalcIO.calculate(dump)_ok' == 'no' - # if tries were successful, try to remove the output file + assert 'TaxCalcIO.analyze(dump)_ok' == 'no' + # if tries were successful, 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 + os.remove(outfilepath) + + +def test_sqldb_option(rawinputfile, reformfile1, assumpfile1): + """ + Test TaxCalcIO output_sqldb option when not writing_output_file. + """ + taxyear = 2021 + tcio = TaxCalcIO(input_data=rawinputfile.name, + tax_year=taxyear, + reform=reformfile1.name, + assump=assumpfile1.name) + assert len(tcio.errmsg) == 0 + tcio.init(input_data=rawinputfile.name, + tax_year=taxyear, + reform=reformfile1.name, + assump=assumpfile1.name, + growdiff_response=None, + aging_input_data=False, + exact_calculations=False) + assert len(tcio.errmsg) == 0 + outfilepath = tcio.output_filepath() + dbfilepath = outfilepath.replace('.csv', '.db') + # --sqldb output + try: + tcio.analyze(writing_output_file=False, output_sqldb=True) + except: # pylint: disable=bare-except + if os.path.isfile(dbfilepath): + try: + os.remove(dbfilepath) + except OSError: + pass # sometimes we can't remove a generated temporary file + assert 'TaxCalcIO.analyze(sqldb)_ok' == 'no' + # if try was successful, remove the db file + if os.path.isfile(dbfilepath): + os.remove(dbfilepath) def test_no_tables_or_graphs(reformfile1): From 63595c20695d2b59c7e61dba3b3bafaddae13c6f Mon Sep 17 00:00:00 2001 From: martinholmer Date: Sun, 23 Apr 2017 21:52:21 -0400 Subject: [PATCH 2/4] Make 'tc --test' exit code indicate PASS or FAIL --- taxcalc/cli/tc.py | 14 ++++++++++---- taxcalc/tests/test_parameters.py | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/taxcalc/cli/tc.py b/taxcalc/cli/tc.py index ed8840a0d..28c1593f7 100644 --- a/taxcalc/cli/tc.py +++ b/taxcalc/cli/tc.py @@ -133,9 +133,11 @@ def main(): output_sqldb=args.sqldb) # compare test output with expected test output if --test option specified if args.test: - compare_test_output_files() - # return no-error exit code - return 0 + retcode = compare_test_output_files() + else: + retcode = 0 + # return exit code + return retcode # end of main function code @@ -165,18 +167,22 @@ def write_test_input_output_files(): def compare_test_output_files(): """ - Compare expected and actual test output files. + Compare expected and actual test output files; + returns 0 if pass test, otherwise returns 1. """ explines = open(EXPECTED_TEST_OUTPUT_FILENAME, 'U').readlines() actlines = open(ACTUAL_TEST_OUTPUT_FILENAME, 'U').readlines() if ''.join(explines) == ''.join(actlines): sys.stdout.write('PASSED TEST\n') + retcode = 0 else: + retcode = 1 sys.stdout.write('FAILED TEST\n') diff = difflib.unified_diff(explines, actlines, fromfile=EXPECTED_TEST_OUTPUT_FILENAME, tofile=ACTUAL_TEST_OUTPUT_FILENAME, n=0) sys.stdout.writelines(diff) + return retcode if __name__ == '__main__': diff --git a/taxcalc/tests/test_parameters.py b/taxcalc/tests/test_parameters.py index aa0375de6..20f710674 100644 --- a/taxcalc/tests/test_parameters.py +++ b/taxcalc/tests/test_parameters.py @@ -10,8 +10,8 @@ import six import numpy as np import pytest -from taxcalc import ParametersBase # pylint: disable=import-error -from taxcalc import Policy, Consumption # pylint: disable=import-error +# pylint: disable=import-error +from taxcalc import ParametersBase, Policy, Consumption def test_instantiation_and_usage(): From 406b365413c983ed44c81e1a5ec86eb203cc488a Mon Sep 17 00:00:00 2001 From: martinholmer Date: Mon, 24 Apr 2017 10:42:02 -0400 Subject: [PATCH 3/4] Round non-integer variables; express MTRs as percents --- taxcalc/taxcalcio.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py index 813e9d876..0c7764200 100644 --- a/taxcalc/taxcalcio.py +++ b/taxcalc/taxcalcio.py @@ -551,16 +551,20 @@ def ceeu_output(cedict): def dump_output(self, mtr_inctax, mtr_paytax): """ - Extract --dump output and return it as pandas DataFrame. + Extract dump output and return it 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 + if varname in Records.INTEGER_VARS: + odf[varname] = vardata + else: + odf[varname] = vardata.round(2) # rounded to nearest cent odf['FLPDYR'] = self.tax_year() # tax calculation year - odf['mtr_inctax'] = mtr_inctax - odf['mtr_paytax'] = mtr_paytax + # mtr values expressed in rounded percentage terms + odf['mtr_inctax'] = (mtr_inctax * 100.0).round(2) + odf['mtr_paytax'] = (mtr_paytax * 100.0).round(2) return odf @staticmethod From 6dbc0cd3896193f5c430a40f40638564f0c93247 Mon Sep 17 00:00:00 2001 From: martinholmer Date: Mon, 24 Apr 2017 11:42:54 -0400 Subject: [PATCH 4/4] Add desc for c05800 calculated variable --- taxcalc/functions.py | 2 +- taxcalc/records_variables.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/taxcalc/functions.py b/taxcalc/functions.py index 4decc6e9b..f55ad82db 100644 --- a/taxcalc/functions.py +++ b/taxcalc/functions.py @@ -776,7 +776,7 @@ def AMT(e07300, dwks13, standard, f6251, c00100, c18300, taxbc, AMT function computes Alternative Minimum Tax taxable income and liability: c62100 is AMT taxable income c09600 is AMT tax liability - c05800 is total (reg + AMT) income tax liability before credits + c05800 is total (regular + AMT) income tax liability before credits Note that line-number variable names refer to (2015) Form 6251. """ diff --git a/taxcalc/records_variables.json b/taxcalc/records_variables.json index d7f900a22..965adaa1c 100644 --- a/taxcalc/records_variables.json +++ b/taxcalc/records_variables.json @@ -601,7 +601,7 @@ }, "c05800": { "type": "float", - "desc": "", + "desc": "Total (regular + AMT) income tax liability before credits", "form": {} }, "c07100": {