Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gui input processing #641

Merged
merged 32 commits into from
Sep 18, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c29c3af
Prototype new GUI processing
Aug 28, 2017
8b1856f
Move new parameter processing to process_model and add comments
Aug 29, 2017
ffd0326
Update to taxcalc 0.10.0 and add local env setup script
Aug 30, 2017
5f5227a
Remove function closure
Aug 30, 2017
c9a8fb2
Update comments in to_json_reform function
Aug 30, 2017
aaad478
Remove setup_local_envs.sh
Aug 30, 2017
fb2eda3
Remove package_up_mods condition and use user_mods instead of mods
Sep 1, 2017
85183a8
Refactor process_model, file_input, personal_results
Sep 5, 2017
35a67d8
Add comments, change function name to submit_reform
Sep 5, 2017
bc783d9
Add error handling logic
Sep 6, 2017
7194532
Upgrade to taxcalc 0.10.1 and improve error handling logic
Sep 8, 2017
174834d
Revert from local taxcalc (version 0.10.2+) to 0.10.1
Sep 11, 2017
8e065fc
Save parsed json style reform
Sep 12, 2017
06aab4c
Bug in saving assumptions_dict
Sep 12, 2017
902a0f1
Fix behavioral response bugs
Sep 12, 2017
2aa97eb
Fix get_reform_from_gui and param parsing bugs
Sep 12, 2017
5b4b39b
Remove unnecessary casting
Sep 12, 2017
0536e20
Fix typo on comment
Sep 12, 2017
5aec330
Remove second nesting of assumptions dict
Sep 12, 2017
2ada5d2
Update test_behavioral.py
Sep 12, 2017
56038d0
Remove extra zero from test value
Sep 13, 2017
b87fc39
Use new parameter processing logic for dynamic params
Sep 13, 2017
ace8ca8
Work on no input case and taxbrain test_views.test_taxbrain_rt_to_pas…
Sep 13, 2017
56a3bb2
Update for boolean switch to 1's and 0's
Sep 14, 2017
439b924
Raise 400 in response to extra input
Sep 14, 2017
e0aaa06
Utilize warning/error message fix in taxcalc 0.10.2
Sep 14, 2017
1d9ff85
Pin taxcalc to version 0.10.2
Sep 14, 2017
6167537
Add xfail for growth param test
Sep 14, 2017
31f32d6
Clean up error handling logic
Sep 14, 2017
fa551d0
Remove versioneer.py
Sep 15, 2017
8d4c3aa
Add docstring to new or edited functions
Sep 17, 2017
650bcb1
Switches are 0/1 instead of True/False
Sep 17, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion conda-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
nomkl
taxcalc==0.9.0
taxcalc==0.10.0
btax==0.1.8
numba==0.33.0
pandas
Expand Down
12 changes: 12 additions & 0 deletions deploy/setup_local_envs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file seems specialized to your development environment -- makes assumptions on paths. Also, not everyone who is working on webapp is also going to be working on ogusa / taxcalc / btax. I think it would be best to leave this out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that's fine with me. I found it to be useful while I was debugging, but I see your point.

conda uninstall taxcalc
cd ~/Documents/Tax-Calculator
python setup.py develop

cd ~/Documents/OG-USA
python setup.py develop

cd ~/Documents/B-Tax
python setup.py develop

echo "Swap back to default aei_dropq env by running ./install_taxbrain_server.sh"
20 changes: 10 additions & 10 deletions webapp/apps/taxbrain/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class DropqCompute(object):
num_budget_years = NUM_BUDGET_YEARS

# Override if needed, e.g. btax
def package_up_vars(self, *args, **kwargs):
return _package_up_vars(*args, **kwargs)
# def package_up_vars(self, *args, **kwargs):
# return _package_up_vars(*args, **kwargs)

def remote_submit_job(self, theurl, data, timeout=TIMEOUT_IN_SECONDS):
response = requests.post(theurl, data=data, timeout=timeout)
Expand Down Expand Up @@ -96,14 +96,14 @@ def submit_calculation(self, mods, first_budget_year, url_template,
pack_up_user_mods=True,
additional_data={}):
data = {}
if pack_up_user_mods:
user_mods = self.package_up_vars(mods, first_budget_year)
if not bool(user_mods):
return False
user_mods = {first_budget_year:user_mods}
else:
user_mods = mods

# if pack_up_user_mods:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to go ahead and delete this code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do. Thanks

# user_mods = self.package_up_vars(mods, first_budget_year)
# if not bool(user_mods):
# return False
# user_mods = {first_budget_year:user_mods}
# else:
# user_mods = mods
user_mods = mods
years = self._get_years(start_budget_year, num_years, first_budget_year)
if use_wnc_offset:
wnc, created = WorkerNodesCounter.objects.get_or_create(singleton_enforce=1)
Expand Down
67 changes: 67 additions & 0 deletions webapp/apps/taxbrain/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,73 @@ def default_taxcalc_data(cls, start_year, metadata=False):
'combined_tax':'Combined Payroll and Individual Income Tax Liability Change',
}


def to_json_reform(fields, start_year):
"""
Convert fields style dictionary to json reform style dictionary
For example:
fields = {u'start_year': u'2017', u'csrfmiddlewaretoken': u'abc123',
u'has_errors': u'False', 'start_year': u'2017',
u'ID_InterestPaid_c_1': u'', u'STD_3': u'*,*,20000',
u'STD_2': u'*,*,10000', u'STD_1': u'*,*,20000',
u'STD_0': u'*,*,10000', u'EITC_ps_cpi': u'1'}
to
reform = {"_STD_0": {"2019": [10000.0]}, "_STD_1": {"2019": [20000.0]},
"_STD_2": {"2019": [10000.0]}, "_STD_3": {"2019": [20000.0]},
"_EITC_ps_cpi': {"2017": 1.0}}
"""
default_params = taxcalc.Policy.default_data(start_year=start_year,
metadata=True)
ignore = (u'has_errors', u'csrfmiddlewaretoken', u'start_year',
u'full_calc', u'quick_calc', 'first_year', '_state',
'creation_date', 'id')


def get_default_policy_param_name(param):
if '_' + param in default_params:
return '_' + param
param_pieces = param.split('_')
end_piece = param_pieces[-1]
no_suffix = '_' + '_'.join(param_pieces[:-1])
if end_piece == 'cpi':
if no_suffix in default_params:
return '_' + param
else:
msg = "Received unexpected parameter: {}"
raise ValueError(msg.format(param))
if no_suffix in default_params:
try:
ix = int(end_piece)
except ValueError:
msg = "Parsing {}: Expected integer for index but got {}"
raise ValueError(msg.format(param, ix))
col_label = default_params[no_suffix]['col_label'][ix]
return no_suffix + '_' + col_label
msg = "Received unexpected parameter: {}"
raise ValueError(msg.format(param))

reform = {}
for param in fields:
if param not in ignore:
param_name = get_default_policy_param_name(param)
reform[param_name] = {}
if not isinstance(fields[param], list):
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])):
if is_wildcard(fields[param][i]):
# may need to do something here
pass
else:
assert (isinstance(fields[param][i], (int, float)) or
isinstance(fields[param][i], bool))
reform[param_name][str(start_year + i)] = [fields[param][i]]
return reform




def expand_1D(x, num_years):
"""
Expand the given data to account for the given number of budget years.
Expand Down
116 changes: 77 additions & 39 deletions webapp/apps/taxbrain/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
from .forms import PersonalExemptionForm, has_field_errors
from .models import TaxSaveInputs, OutputUrl, JSONReformTaxCalculator, ErrorMessageTaxCalculator
from .helpers import (default_policy, taxcalc_results_to_tables, format_csv,
is_wildcard, convert_val, make_bool, nested_form_parameters)
is_wildcard, convert_val, make_bool, nested_form_parameters,
to_json_reform)
from .compute import DropqCompute, MockCompute, JobFailError

dropq_compute = DropqCompute()
Expand Down Expand Up @@ -70,20 +71,20 @@ def log_ip(request):
print("BEGIN DROPQ WORK FROM: unknown IP")


def benefit_surtax_fixup(request, reform, model):
def benefit_surtax_fixup(request, reform, model, name="ID_BenefitSurtax_Switch"):
"""
Take the incoming POST, the user reform, and the TaxSaveInputs
model and fixup the switches _0, ..., _6 to one array of
bools. Also set the model values correctly based on incoming
POST
"""
_ids = ['ID_BenefitSurtax_Switch_' + str(i) for i in range(7)]
_ids = [name + '_' + str(i) for i in range(7)]
values_from_model = [[reform[_id][0] for _id in _ids]]
final_values = [[True if _id in request else switch for (switch, _id) in zip(values_from_model[0], _ids)]]
reform['ID_BenefitSurtax_Switch'] = final_values
for _id, val in zip(_ids, final_values[0]):
del reform[_id]
reform[_id] = [val]
setattr(model, _id, unicode(val))
return reform

def amt_fixup(request, reform, model):
"""
Expand Down Expand Up @@ -161,42 +162,79 @@ def process_model(model, start_year, stored_errors=None, request=None,

worker_data = {k:v for k, v in curr_dict.items() if not (v == [] or v == None)}
if request:
benefit_surtax_fixup(request.REQUEST, worker_data, model)
worker_data = benefit_surtax_fixup(request.REQUEST, worker_data, model,
name="ID_BenefitSurtax_Switch")
worker_data = benefit_surtax_fixup(request.REQUEST, worker_data, model,
name="ID_BenefitCap_Switch")
amt_fixup(request.REQUEST, worker_data, model)
# start calc job
if do_full_calc:
submitted_ids, max_q_length = dropq_compute.submit_dropq_calculation(worker_data, int(start_year))
else:
submitted_ids, max_q_length = dropq_compute.submit_dropq_small_calculation(worker_data, int(start_year))

if not submitted_ids:
raise JobFailError("couldn't submit ids")
else:
job_ids = denormalize(submitted_ids)
model.job_ids = job_ids
model.first_year = int(start_year)
model.quick_calc = not do_full_calc
model.save()
unique_url = OutputUrl()
if user:
unique_url.user = user
elif request and request.user.is_authenticated():
current_user = User.objects.get(pk=request.user.id)
unique_url.user = current_user

if unique_url.taxcalc_vers != None:
pass
policy_dict = to_json_reform(worker_data, int(start_year))
policy_dict = {"policy": policy_dict}
policy_dict = taxcalc.Calculator.read_json_param_files(json.dumps(policy_dict),
None,
arrays_not_lists=False)
# separate policy reform and assumptions data
reforms = policy_dict["policy"]
assumptions = {k: v for k, v in policy_dict.items() if k != "policy"}

# TODO: handle errors cound in TaxBrain and Tax-Calculator
error_messages = {}
if error_messages:
has_errors = True
errors = ["{} {}".format(k, v) for k, v in error_messages.items()]
else:
unique_url.taxcalc_vers = taxcalc_version
log_ip(request)
if do_full_calc:
submitted_ids, max_q_length = dropq_compute.submit_dropq_calculation(
reforms,
int(start_year),
is_file=False,
additional_data=assumptions
)
else:
submitted_ids, max_q_length = dropq_compute.submit_dropq_small_calculation(
reforms,
int(start_year),
is_file=False,
additional_data=assumptions
)

if not submitted_ids:
raise JobFailError("couldn't submit ids")
# TODO: save inputs for user edit page
job_ids = denormalize(submitted_ids)
json_reform = JSONReformTaxCalculator()
json_reform.reform_text = json.dumps(policy_dict)
json_reform.assumption_text = ""
json_reform.raw_reform_text = ""#json.dumps(worker_data)
json_reform.raw_assumption_text = ""
json_reform.save()

model.job_ids = job_ids
model.first_year = int(start_year)
model.quick_calc = not do_full_calc
model.save()
unique_url = OutputUrl()
if user:
unique_url.user = user
elif request and request.user.is_authenticated():
current_user = User.objects.get(pk=request.user.id)
unique_url.user = current_user

if unique_url.taxcalc_vers != None:
pass
else:
unique_url.taxcalc_vers = taxcalc_version

unique_url.unique_inputs = model
unique_url.model_pk = model.pk
cur_dt = datetime.datetime.utcnow()
future_offset = datetime.timedelta(seconds=((2 + max_q_length) * JOB_PROC_TIME_IN_SECONDS))
expected_completion = cur_dt + future_offset
unique_url.exp_comp_datetime = expected_completion
unique_url.save()
return unique_url

unique_url.unique_inputs = model
unique_url.model_pk = model.pk
cur_dt = datetime.datetime.utcnow()
future_offset = datetime.timedelta(seconds=((2 + max_q_length) * JOB_PROC_TIME_IN_SECONDS))
expected_completion = cur_dt + future_offset
unique_url.exp_comp_datetime = expected_completion
unique_url.save()
return unique_url

def file_input(request):
"""
Expand Down Expand Up @@ -383,8 +421,8 @@ def personal_results(request):
form_personal_exemp = personal_inputs
else:
# received POST but invalid results, return to form with errors
form_personal_exemp = personal_inputs

form_personal_exemp = PersonalExemptionForm(first_year=start_year)
# no_inputs = True
else:
params = parse_qs(urlparse(request.build_absolute_uri()).query)
if 'start_year' in params and params['start_year'][0] in START_YEARS:
Expand Down