Skip to content

Commit

Permalink
Merge pull request #790 from hdoupe/reverse_character
Browse files Browse the repository at this point in the history
Merged #790
  • Loading branch information
hdoupe authored Jan 5, 2018
2 parents f83af8e + 25a6837 commit 2a3cd78
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 16 deletions.
1 change: 1 addition & 0 deletions templates/taxbrain/input_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ <h3>User tips</h3>
<strong>DO NOT</strong> use commas as thousands separators. </li>
<li> Express rates as decimals. </li>
<li>You can represent a value that would otherwise manifest itself in a year with the <strong>*</strong> operator. So, for example, if you enter <strong>0.1, *, 0.2</strong> in a tax rate field, the rate will be 10 percent in the Start Year, 10 percent a year after the Start Year, and 20 percent two years after the Start Year and forward. If you enter <strong>100, *, 200</strong> in a CPI-indexed field for a credit amount, the credit will be $100 in the Start Year, $100*(1+CPI) the next year, and $200 the following year.</li>
<li>You may specify a parameter change in the year before the Start Year with the <strong> < </strong> operator. For example, if you set the Start Year as 2018 and a Parameter Indexing Offset parameter to <strong> <,-0.0025,-0.001 </strong> then this sets the Offset to -0.0025 in 2017 and -0.001 in 2018.</li>
<li> <a href = "//ospc.org/docs" target="_blank">Read the Docs</a> </li>
<li> <a href = "//docs.google.com/forms/d/e/1FAIpQLSfvKEsQZNe9_16FhKNZcEyv3C8qXZhBfrqLxSd4VR97w92moQ/viewform" target="_blank">Leave Feedback</a> </li>
</ul>
Expand Down
5 changes: 3 additions & 2 deletions webapp/apps/taxbrain/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,12 @@ def do_taxcalc_validations(self):
continue

submitted_col_values_raw = self.cleaned_data[col_field.id]

if len(submitted_col_values_raw) > 0 and submitted_col_values_raw not in BOOLEAN_FLAGS:
try:
INPUT.parseString(submitted_col_values_raw)
except ParseException as pe:
# reverse character is not at the beginning
assert submitted_col_values_raw.find('<') <= 0
except (ParseException, AssertionError):
# Parse Error - we don't recognize what they gave us
self.add_error(col_field.id, "Unrecognized value: {}".format(submitted_col_values_raw))

Expand Down
36 changes: 33 additions & 3 deletions webapp/apps/taxbrain/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@
DEC_POINT = pp.Word('.', exact=1)
FLOAT_LIT_FULL = pp.Word(pp.nums + '.' + pp.nums)
COMMON = pp.Word(",", exact=1)
REVERSE = pp.Word("<") + COMMON

VALUE = WILDCARD | NEG_DASH | FLOAT_LIT_FULL | FLOAT_LIT | INT_LIT
MORE_VALUES = COMMON + VALUE

BOOL = WILDCARD | TRUE | FALSE
MORE_BOOLS = COMMON + BOOL
INPUT = BOOL + pp.ZeroOrMore(MORE_BOOLS) | VALUE + pp.ZeroOrMore(MORE_VALUES)
INPUT = pp.Optional(REVERSE) + BOOL + pp.ZeroOrMore(MORE_BOOLS) | pp.Optional(REVERSE) + VALUE + pp.ZeroOrMore(MORE_VALUES)

TRUE_REGEX = re.compile('(?i)true')
FALSE_REGEX = re.compile('(?i)false')
Expand All @@ -54,6 +55,14 @@ def is_wildcard(x):
else:
return False


def is_reverse(x):
if isinstance(x, six.string_types):
return x in ['<', u'<'] or x.strip() in ['<', u'<']
else:
return False


def check_wildcards(x):
if isinstance(x, list):
return any([check_wildcards(i) for i in x])
Expand Down Expand Up @@ -88,6 +97,8 @@ def make_bool(x):
def convert_val(x):
if is_wildcard(x):
return x
if is_reverse(x):
return x
try:
return float(x)
except ValueError:
Expand Down Expand Up @@ -399,6 +410,7 @@ def to_json_reform(fields, start_year, cls=taxcalc.Policy):
returns json style reform
"""
map_back_to_tb = {}
number_reverse_operators = 1
default_params = cls.default_data(start_year=start_year,
metadata=True)
ignore = (u'has_errors', u'csrfmiddlewaretoken', u'start_year',
Expand All @@ -417,14 +429,32 @@ def to_json_reform(fields, start_year, cls=taxcalc.Policy):
assert isinstance(fields[param], bool) and param.endswith('_cpi')
reform[param_name][str(start_year)] = fields[param]
continue
for i in range(len(fields[param])):
i = 0
while i < len(fields[param]):
if is_wildcard(fields[param][i]):
# may need to do something here
pass
elif is_reverse(fields[param][i]):
# only the first character can be a reverse char
# and there must be a following character
assert len(fields[param]) > 1
# set value for parameter in start_year - 1
assert (isinstance(fields[param][i + 1], (int, float)) or
isinstance(fields[param][i + 1], bool))
reform[param_name][str(start_year - 1)] = \
[fields[param][i + 1]]

# realign year and parameter indices
for op in (0, number_reverse_operators + 1):
fields[param].pop(0)
continue
else:
assert (isinstance(fields[param][i], (int, float)) or
isinstance(fields[param][i], bool))
reform[param_name][str(start_year + i)] = [fields[param][i]]
reform[param_name][str(start_year + i)] = \
[fields[param][i]]

i += 1

return reform, map_back_to_tb

Expand Down
2 changes: 1 addition & 1 deletion webapp/apps/taxbrain/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def numberfy(x):
COMMASEP_REGEX = "(\\d*\\.\\d+|\\d+)|((?i)(true|false))"

class CommaSeparatedField(models.CharField):
default_validators = [validators.RegexValidator(regex="(\\d*\\.\\d+|\\d+)|((?i)(true|false))")]
default_validators = [validators.RegexValidator(regex="(<,)|(\\d*\\.\\d+|\\d+)|((?i)(true|false))")]
description = "A comma separated field that allows multiple floats."

def __init__(self, verbose_name=None, name=None, **kwargs):
Expand Down
15 changes: 12 additions & 3 deletions webapp/apps/taxbrain/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import taxcalc
import pyparsing as pp
from ..helpers import (nested_form_parameters, rename_keys, INPUT, make_bool,
TaxCalcParam)
is_reverse, TaxCalcParam)


CURRENT_LAW_POLICY = """
{
Expand Down Expand Up @@ -139,12 +140,14 @@ def test_rename_keys(monkeypatch):
'False', 'false', 'FALSE','faLSe',
'true,*', '*, true', '*,*,false',
'true,*,false,*,*,true',
'1,*,False', '0.0,True', '1.0,False']
'1,*,False', '0.0,True', '1.0,False',
'<,True', '<,1']
)
def test_parsing_pass(item):
INPUT.parseString(item)

def test_parsing_fail():
@pytest.mark.parametrize('item', ['abc', '<,', '<', '1,<', '0,<,1', 'True,<', '-0.002,<,-0.001'])
def test_parsing_fail(item):
with pytest.raises(pp.ParseException):
INPUT.parseString('abc')

Expand All @@ -166,6 +169,12 @@ def test_make_bool_fail(item):
with pytest.raises((ValueError, TypeError)):
make_bool(item)

@pytest.mark.parametrize(
'item,exp',
[('<', True), ('a', False), ('1', False), (1, False), (False, False)])
def test_is_reverse(item, exp):
assert is_reverse(item) is exp


def test_make_taxcalc_param():
"""
Expand Down
57 changes: 54 additions & 3 deletions webapp/apps/taxbrain/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,35 @@ def test_taxbrain_wildcard_params_with_validation_gives_error(self):
assert response.context['has_errors'] is True


def test_taxbrain_wildcard_in_validation_params_OK(self):
def test_taxbrain_spec_operators_in_validation_params_OK(self):
"""
Set upper threshold for income tax bracket 1 to *, 38000
Set upper threshold for income tax bracket 2 to *, *, 39500
should be OK
"""
data = get_post_data(START_YEAR, _ID_BenefitSurtax_Switches=False)
mod = {u'II_brk1_0': [u'*, *, 38000'],
u'II_brk2_0': [u'*, *, 39500']}
u'II_brk2_0': [u'*, *, 39500'],
u'cpi_offset': [u'<,-0.0025'],
u'FICA_ss_trt': [u'< ,0.1,*,0.15,0.2']}
data.update(mod)
do_micro_sim(self.client, data)
result = do_micro_sim(self.client, data)

truth_mods = {
START_YEAR - 1: {
'_cpi_offset': [-0.0025],
'_FICA_ss_trt': [0.1]
},
START_YEAR + 1: {
'_FICA_ss_trt': [0.15]
},
START_YEAR + 2: {
'_FICA_ss_trt': [0.2]
}
}

check_posted_params(result['tb_dropq_compute'], truth_mods, START_YEAR)



def test_taxbrain_wildcard_in_validation_params_gives_error(self):
Expand All @@ -395,6 +413,39 @@ def test_taxbrain_wildcard_in_validation_params_gives_error(self):
self.assertEqual(response.status_code, 200)
assert response.context['has_errors'] is True


def test_taxbrain_improper_reverse_gives_error1(self):
"""
Check reverse operator post without other numbers throws error
"""
#Monkey patch to mock out running of compute jobs
get_dropq_compute_from_module('webapp.apps.taxbrain.views')

data = get_post_data(START_YEAR, _ID_BenefitSurtax_Switches=False)
mod = {u'cpi_offset': [u'<,']}
data.update(mod)

response = self.client.post('/taxbrain/', data)
# Check that redirect happens
self.assertEqual(response.status_code, 200)
assert response.context['has_errors'] is True

def test_taxbrain_improper_reverse_gives_error2(self):
"""
Check reverse operator not in first position throws error
"""
#Monkey patch to mock out running of compute jobs
get_dropq_compute_from_module('webapp.apps.taxbrain.views')

data = get_post_data(START_YEAR, _ID_BenefitSurtax_Switches=False)
mod = {u'cpi_offset': [u'-0.002,<,-0.001']}
data.update(mod)

response = self.client.post('/taxbrain/', data)
# Check that redirect happens
self.assertEqual(response.status_code, 200)
assert response.context['has_errors'] is True

def test_taxbrain_bool_separated_values(self):
"""
Test _DependentCredit_before_CTC can be posted as comma separated
Expand Down
3 changes: 2 additions & 1 deletion webapp/apps/taxbrain/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,8 @@ def edit_personal_results(request, pk):
'webapp_version': webapp_vers_disp,
'start_years': START_YEARS,
'start_year': str(start_year),
'is_edit_page': True
'is_edit_page': True,
'has_errors': False
}

return render(request, 'taxbrain/input_form.html', init_context)
Expand Down
11 changes: 8 additions & 3 deletions webapp/apps/test_assets/test_reform.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,10 @@
}

test_coverage_fields = dict(
cpi_offset = ['<', -0.0025],
CG_nodiff = [False],
FICA_ss_trt = [u'*', 0.1, u'*', 0.2],
FICA_ss_trt = ['<',0.1,'*',0.15,0.2],
FICA_mc_trt = ['<',0.1,0.15],
STD_0 = [8000.0, '*', 10000.0],
ID_BenefitSurtax_Switch_0 = [True],
ID_Charity_c_cpi = True,
Expand All @@ -214,8 +216,10 @@
)

test_coverage_reform = {
'_cpi_offset': {'2016': [-0.0025]},
'_CG_nodiff': {'2017': [False]},
'_FICA_ss_trt': {'2020': [0.2], '2018': [0.1]},
'_FICA_ss_trt': {'2016': [0.1], '2018': [0.15], '2019': [0.2]},
'_FICA_mc_trt': {'2016': [0.1], '2017': [0.15]},
'_STD_single': {'2017': [8000.0], '2019': [10000.0]},
'_ID_Charity_c_cpi': {'2017': True},
'_ID_BenefitSurtax_Switch_medical': {'2017': [True]},
Expand Down Expand Up @@ -267,7 +271,8 @@
u'_ID_BenefitCap_Switch_realestate': 'ID_BenefitCap_Switch_2',
u'_STD_single': 'STD_0',
u'_STD_headhousehold': 'STD_3',
u'_II_brk4_single': 'II_brk4_0'
u'_II_brk4_single': 'II_brk4_0',
u'_cpi_offset': 'cpi_offset'
}

test_coverage_json_reform = """
Expand Down

0 comments on commit 2a3cd78

Please sign in to comment.