taxcalc 4.2.1__py3-none-any.whl → 4.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taxcalc/__init__.py +1 -1
- taxcalc/assumptions/ASSUMPTIONS.md +53 -0
- taxcalc/assumptions/README.md +17 -0
- taxcalc/assumptions/economic_assumptions_template.json +77 -0
- taxcalc/calcfunctions.py +7 -4
- taxcalc/data.py +10 -5
- taxcalc/policy_current_law.json +2033 -184
- taxcalc/reforms/2017_law.json +125 -0
- taxcalc/reforms/2017_law.out.csv +10 -0
- taxcalc/reforms/ARPA.json +78 -0
- taxcalc/reforms/ARPA.out.csv +10 -0
- taxcalc/reforms/BrownKhanna.json +23 -0
- taxcalc/reforms/BrownKhanna.out.csv +10 -0
- taxcalc/reforms/CARES.json +40 -0
- taxcalc/reforms/CARES.out.csv +10 -0
- taxcalc/reforms/ConsolidatedAppropriationsAct2021.json +15 -0
- taxcalc/reforms/ConsolidatedAppropriationsAct2021.out.csv +10 -0
- taxcalc/reforms/Larson2019.json +36 -0
- taxcalc/reforms/Larson2019.out.csv +10 -0
- taxcalc/reforms/README.md +22 -0
- taxcalc/reforms/REFORMS.md +92 -0
- taxcalc/reforms/Renacci.json +61 -0
- taxcalc/reforms/Renacci.out.csv +10 -0
- taxcalc/reforms/SandersDeFazio.json +15 -0
- taxcalc/reforms/SandersDeFazio.out.csv +10 -0
- taxcalc/reforms/TCJA.json +160 -0
- taxcalc/reforms/TCJA.md +48 -0
- taxcalc/reforms/TCJA.out.csv +10 -0
- taxcalc/reforms/Trump2016.json +71 -0
- taxcalc/reforms/Trump2016.out.csv +10 -0
- taxcalc/reforms/Trump2017.json +51 -0
- taxcalc/reforms/Trump2017.out.csv +10 -0
- taxcalc/reforms/archive/Clinton2016.json +56 -0
- taxcalc/reforms/archive/RyanBrady.json +104 -0
- taxcalc/reforms/archive/TCJA_House.json +144 -0
- taxcalc/reforms/archive/TCJA_House_Amended.json +152 -0
- taxcalc/reforms/archive/TCJA_Reconciliation.json +187 -0
- taxcalc/reforms/archive/TCJA_Senate.json +116 -0
- taxcalc/reforms/archive/TCJA_Senate_111417.json +169 -0
- taxcalc/reforms/archive/TCJA_Senate_120117.json +174 -0
- taxcalc/reforms/cases.csv +10 -0
- taxcalc/reforms/clp.out.csv +10 -0
- taxcalc/reforms/ext.json +59 -0
- taxcalc/reforms/growfactors_ext.csv +65 -0
- taxcalc/reforms/ptaxes0.json +37 -0
- taxcalc/reforms/ptaxes0.out.csv +10 -0
- taxcalc/reforms/ptaxes1.json +21 -0
- taxcalc/reforms/ptaxes1.out.csv +10 -0
- taxcalc/reforms/ptaxes2.json +18 -0
- taxcalc/reforms/ptaxes2.out.csv +10 -0
- taxcalc/reforms/ptaxes3.json +28 -0
- taxcalc/reforms/ptaxes3.out.csv +10 -0
- taxcalc/reforms/rounding2022.json +153 -0
- taxcalc/reforms/rounding2022.out.csv +10 -0
- taxcalc/tests/benefits_expect.csv +169 -0
- taxcalc/tests/cmpi_cps_expect.txt +132 -0
- taxcalc/tests/cmpi_puf_expect.txt +132 -0
- taxcalc/tests/conftest.py +143 -0
- taxcalc/tests/cpscsv_agg_expect.csv +26 -0
- taxcalc/tests/puf_var_correl_coeffs_2016.csv +80 -0
- taxcalc/tests/puf_var_wght_means_by_year.csv +80 -0
- taxcalc/tests/pufcsv_agg_expect.csv +26 -0
- taxcalc/tests/pufcsv_mtr_expect.txt +63 -0
- taxcalc/tests/reforms.json +649 -0
- taxcalc/tests/reforms_expect.csv +65 -0
- taxcalc/tests/test_4package.py +67 -0
- taxcalc/tests/test_benefits.py +86 -0
- taxcalc/tests/test_calcfunctions.py +871 -0
- taxcalc/tests/test_calculator.py +1021 -0
- taxcalc/tests/test_compare.py +336 -0
- taxcalc/tests/test_compatible_data.py +338 -0
- taxcalc/tests/test_consumption.py +144 -0
- taxcalc/tests/test_cpscsv.py +163 -0
- taxcalc/tests/test_data.py +133 -0
- taxcalc/tests/test_decorators.py +332 -0
- taxcalc/tests/test_growdiff.py +102 -0
- taxcalc/tests/test_growfactors.py +94 -0
- taxcalc/tests/test_parameters.py +617 -0
- taxcalc/tests/test_policy.py +1575 -0
- taxcalc/tests/test_puf_var_stats.py +194 -0
- taxcalc/tests/test_pufcsv.py +385 -0
- taxcalc/tests/test_records.py +234 -0
- taxcalc/tests/test_reforms.py +385 -0
- taxcalc/tests/test_responses.py +41 -0
- taxcalc/tests/test_taxcalcio.py +755 -0
- taxcalc/tests/test_tmdcsv.py +38 -0
- taxcalc/tests/test_utils.py +792 -0
- taxcalc/tmd_growfactors.csv +54 -54
- taxcalc/tmd_weights.csv.gz +0 -0
- taxcalc/validation/CSV_INPUT_VARS.md +29 -0
- taxcalc/validation/CSV_OUTPUT_VARS.md +63 -0
- taxcalc/validation/README.md +68 -0
- taxcalc/validation/taxsim35/Differences_Explained.md +54 -0
- taxcalc/validation/taxsim35/README.md +139 -0
- taxcalc/validation/taxsim35/expected_differences/a17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a20-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/a21-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b20-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/b21-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c17-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c18-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/expected_differences/c19-taxdiffs-expect.csv +25 -0
- taxcalc/validation/taxsim35/input_setup.py +67 -0
- taxcalc/validation/taxsim35/main_comparison.py +183 -0
- taxcalc/validation/taxsim35/prepare_taxcalc_input.py +161 -0
- taxcalc/validation/taxsim35/process_taxcalc_output.py +140 -0
- taxcalc/validation/taxsim35/taxsim_emulation.json +49 -0
- taxcalc/validation/taxsim35/taxsim_input.py +321 -0
- taxcalc/validation/taxsim35/tc_sims.py +98 -0
- taxcalc/validation/taxsim35/tests_35.py +80 -0
- taxcalc/validation/tests_35.sh +13 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/METADATA +3 -4
- taxcalc-4.2.2.dist-info/RECORD +144 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/WHEEL +1 -1
- taxcalc-4.2.1.dist-info/RECORD +0 -34
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/LICENSE +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/entry_points.txt +0 -0
- {taxcalc-4.2.1.dist-info → taxcalc-4.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,617 @@
|
|
1
|
+
"""
|
2
|
+
Tests for Tax-Calculator Parameters class and JSON parameter files.
|
3
|
+
"""
|
4
|
+
# CODING-STYLE CHECKS:
|
5
|
+
# pycodestyle test_parameters.py
|
6
|
+
# pylint --disable=locally-disabled test_parameters.py
|
7
|
+
|
8
|
+
import copy
|
9
|
+
import os
|
10
|
+
import json
|
11
|
+
import math
|
12
|
+
import tempfile
|
13
|
+
import numpy as np
|
14
|
+
import paramtools
|
15
|
+
import pytest
|
16
|
+
# pylint: disable=import-error
|
17
|
+
from taxcalc import (
|
18
|
+
Parameters,
|
19
|
+
Policy,
|
20
|
+
Consumption,
|
21
|
+
GrowFactors,
|
22
|
+
is_paramtools_format,
|
23
|
+
)
|
24
|
+
from taxcalc.growdiff import GrowDiff
|
25
|
+
|
26
|
+
|
27
|
+
# Test specification and use of simple Parameters-derived class that has
|
28
|
+
# no vector parameters and has no (wage or price) indexed parameters.
|
29
|
+
# This derived class is called Params and it contains one of each of the
|
30
|
+
# four types of parameters.
|
31
|
+
#
|
32
|
+
# The following pytest fixture specifies the JSON DEFAULTS file for the
|
33
|
+
# Params class, which is defined in the test_params_class function.
|
34
|
+
|
35
|
+
|
36
|
+
PARAMS_JSON = json.dumps({
|
37
|
+
"schema": {
|
38
|
+
"labels": {
|
39
|
+
"year": {
|
40
|
+
"type": "int",
|
41
|
+
"validators": {"range": {"min": 2001, "max": 2010}}
|
42
|
+
},
|
43
|
+
"label": {
|
44
|
+
"type": "str",
|
45
|
+
"validators": {"choice": {"choices": ["label1", "label2"]}}
|
46
|
+
}
|
47
|
+
},
|
48
|
+
"operators": {
|
49
|
+
"array_first": True,
|
50
|
+
"label_to_extend": "year"
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"real_param": {
|
54
|
+
"title": "Real (float) parameter",
|
55
|
+
"description": "",
|
56
|
+
"type": "float",
|
57
|
+
"value": 0.5,
|
58
|
+
"validators": {"range": {"min": 0, "max": 1}}
|
59
|
+
},
|
60
|
+
"int_param": {
|
61
|
+
"title": "Integer parameter",
|
62
|
+
"description": "",
|
63
|
+
"type": "int",
|
64
|
+
"value": 2,
|
65
|
+
"validators": {"range": {"min": 0, "max": 9}}
|
66
|
+
},
|
67
|
+
"bool_param": {
|
68
|
+
"title": "Boolean parameter",
|
69
|
+
"description": "",
|
70
|
+
"type": "bool",
|
71
|
+
"value": True,
|
72
|
+
},
|
73
|
+
"str_param": {
|
74
|
+
"title": "String parameter",
|
75
|
+
"description": "",
|
76
|
+
"type": "str",
|
77
|
+
"value": "linear",
|
78
|
+
"validators": {"choice": {"choices": ["linear", "nonlinear", "cubic"]}}
|
79
|
+
},
|
80
|
+
"label_param": {
|
81
|
+
"title": "Parameter that uses labels.",
|
82
|
+
"description": "",
|
83
|
+
"type": "int",
|
84
|
+
"value": [
|
85
|
+
{"label": "label1", "year": 2001, "value": 2},
|
86
|
+
{"label": "label2", "year": 2001, "value": 3}
|
87
|
+
],
|
88
|
+
"validators": {"range": {"min": 0, "max": 9}}
|
89
|
+
}
|
90
|
+
})
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.fixture(scope='module', name='params_json_file')
|
94
|
+
def fixture_params_json_file():
|
95
|
+
"""
|
96
|
+
Define JSON DEFAULTS file for Parameters-derived Params class.
|
97
|
+
"""
|
98
|
+
with tempfile.NamedTemporaryFile(mode='a', delete=False) as pfile:
|
99
|
+
pfile.write(PARAMS_JSON + '\n')
|
100
|
+
pfile.close()
|
101
|
+
yield pfile
|
102
|
+
os.remove(pfile.name)
|
103
|
+
|
104
|
+
|
105
|
+
@pytest.mark.parametrize("revision, expect", [
|
106
|
+
({}, ""),
|
107
|
+
({'real_param': {2004: 1.9}}, "error"),
|
108
|
+
({'int_param': {2004: [3.6]}}, "raise"),
|
109
|
+
({"int_param": {2004: [3]}}, "raise"),
|
110
|
+
({"label_param": {2004: [1, 2]}}, "noerror"),
|
111
|
+
({"label_param": {2004: [[1, 2]]}}, "raise"),
|
112
|
+
({"label_param": {2004: [1, 2, 3]}}, "raise"),
|
113
|
+
({'bool_param': {2004: [4.9]}}, "raise"),
|
114
|
+
({'str_param': {2004: [9]}}, "raise"),
|
115
|
+
({'str_param': {2004: 'nonlinear'}}, "noerror"),
|
116
|
+
({'str_param': {2004: 'unknownvalue'}}, "error"),
|
117
|
+
({'str_param': {2004: ['nonlinear']}}, "raise"),
|
118
|
+
({'real_param': {2004: 'linear'}}, "raise"),
|
119
|
+
({'real_param': {2004: [0.2, 0.3]}}, "raise"),
|
120
|
+
({'real_param-indexed': {2004: True}}, "raise"),
|
121
|
+
({'unknown_param-indexed': {2004: False}}, "raise")
|
122
|
+
])
|
123
|
+
def test_params_class(revision, expect, params_json_file):
|
124
|
+
"""
|
125
|
+
Specifies Params class and tests it.
|
126
|
+
"""
|
127
|
+
|
128
|
+
class Params(Parameters):
|
129
|
+
"""
|
130
|
+
The Params class is derived from the abstract base Parameter class.
|
131
|
+
"""
|
132
|
+
DEFAULTS_FILE_NAME = params_json_file.name
|
133
|
+
DEFAULTS_FILE_PATH = ''
|
134
|
+
START_YEAR = 2001
|
135
|
+
LAST_YEAR = 2010
|
136
|
+
NUM_YEARS = LAST_YEAR - START_YEAR + 1
|
137
|
+
|
138
|
+
def __init__(self):
|
139
|
+
super().__init__()
|
140
|
+
self.initialize(Params.START_YEAR, Params.NUM_YEARS)
|
141
|
+
|
142
|
+
def update_params(self, revision,
|
143
|
+
print_warnings=True, raise_errors=True):
|
144
|
+
"""
|
145
|
+
Update parameters given specified revision dictionary.
|
146
|
+
"""
|
147
|
+
self._update(revision, print_warnings, raise_errors)
|
148
|
+
|
149
|
+
# test Params class
|
150
|
+
prms = Params()
|
151
|
+
|
152
|
+
with pytest.raises(NotImplementedError):
|
153
|
+
prms.set_rates()
|
154
|
+
|
155
|
+
if revision == {}:
|
156
|
+
assert isinstance(prms, Params)
|
157
|
+
assert prms.start_year == 2001
|
158
|
+
assert prms.current_year == 2001
|
159
|
+
assert prms.end_year == 2010
|
160
|
+
assert prms.inflation_rates() == list()
|
161
|
+
assert prms.wage_growth_rates() == list()
|
162
|
+
prms.set_year(2010)
|
163
|
+
assert prms.current_year == 2010
|
164
|
+
with pytest.raises(paramtools.ValidationError):
|
165
|
+
prms.set_year(2011)
|
166
|
+
return
|
167
|
+
|
168
|
+
if expect == 'raise':
|
169
|
+
with pytest.raises(paramtools.ValidationError):
|
170
|
+
prms.update_params(revision)
|
171
|
+
elif expect == 'noerror':
|
172
|
+
prms.update_params(revision)
|
173
|
+
assert not prms.errors
|
174
|
+
elif expect == 'error':
|
175
|
+
with pytest.raises(paramtools.ValidationError):
|
176
|
+
prms.update_params(revision)
|
177
|
+
assert prms.errors
|
178
|
+
elif expect == 'warn':
|
179
|
+
with pytest.raises(paramtools.ValidationError):
|
180
|
+
prms.update_params(revision)
|
181
|
+
assert prms.warnings
|
182
|
+
|
183
|
+
|
184
|
+
@pytest.mark.parametrize("fname",
|
185
|
+
[("consumption.json"),
|
186
|
+
("policy_current_law.json"),
|
187
|
+
("growdiff.json")])
|
188
|
+
def test_json_file_contents(tests_path, fname):
|
189
|
+
"""
|
190
|
+
Check contents of JSON parameter files in Tax-Calculator/taxcalc directory.
|
191
|
+
"""
|
192
|
+
first_year = Policy.JSON_START_YEAR
|
193
|
+
last_known_year = Policy.LAST_KNOWN_YEAR # for indexed parameter values
|
194
|
+
known_years = set(range(first_year, last_known_year + 1))
|
195
|
+
long_params = ['II_brk1', 'II_brk2', 'II_brk3', 'II_brk4',
|
196
|
+
'II_brk5', 'II_brk6', 'II_brk7',
|
197
|
+
'PT_brk1', 'PT_brk2', 'PT_brk3', 'PT_brk4',
|
198
|
+
'PT_brk5', 'PT_brk6', 'PT_brk7',
|
199
|
+
'PT_qbid_taxinc_thd',
|
200
|
+
'ALD_BusinessLosses_c',
|
201
|
+
'STD', 'II_em', 'II_em_ps',
|
202
|
+
'AMT_em', 'AMT_em_ps', 'AMT_em_pe',
|
203
|
+
'ID_ps', 'ID_AllTaxes_c']
|
204
|
+
# for TCJA-reverting long_params
|
205
|
+
long_known_years = set(range(first_year, last_known_year + 1))
|
206
|
+
long_known_years.add(2026)
|
207
|
+
# check elements in each parameter sub-dictionary
|
208
|
+
failures = ''
|
209
|
+
with open(os.path.join(tests_path, "..", fname)) as f:
|
210
|
+
allparams = json.loads(f.read())
|
211
|
+
for pname in allparams:
|
212
|
+
if pname == "schema":
|
213
|
+
continue
|
214
|
+
# check that param contains required keys
|
215
|
+
param = allparams[pname]
|
216
|
+
# check that indexable and indexed are False in many files
|
217
|
+
if fname != 'policy_current_law.json':
|
218
|
+
assert param.get('indexable', False) is False
|
219
|
+
assert param.get('indexed', False) is False
|
220
|
+
# check that indexable is True when indexed is True
|
221
|
+
if param.get('indexed', False) and not param.get('indexable', False):
|
222
|
+
msg = 'param:<{}>; indexed={}; indexable={}'
|
223
|
+
fail = msg.format(pname,
|
224
|
+
param.get('indexed', False),
|
225
|
+
param.get('indexable', False))
|
226
|
+
failures += fail + '\n'
|
227
|
+
# check that indexable param has value_type float
|
228
|
+
if param.get('indexable', False) and param['type'] != 'float':
|
229
|
+
msg = 'param:<{}>; type={}; indexable={}'
|
230
|
+
fail = msg.format(pname, param['type'],
|
231
|
+
param.get('indexable', False))
|
232
|
+
failures += fail + '\n'
|
233
|
+
# ensure that indexable is False when value_type is not real
|
234
|
+
if param.get('indexable', False) and param['type'] != 'float':
|
235
|
+
msg = 'param:<{}>; indexable={}; type={}'
|
236
|
+
fail = msg.format(pname,
|
237
|
+
param.get('indexable', False),
|
238
|
+
param['value_type'])
|
239
|
+
failures += fail + '\n'
|
240
|
+
if fname == "consumption.json":
|
241
|
+
o = Consumption()
|
242
|
+
elif fname == "policy_current_law.json":
|
243
|
+
o = Policy()
|
244
|
+
elif fname == "growdiff.json":
|
245
|
+
o = GrowDiff()
|
246
|
+
param_list = []
|
247
|
+
for k in o:
|
248
|
+
if k[0].isupper(): # find parameters by case of first letter
|
249
|
+
param_list.append(k)
|
250
|
+
for param in param_list:
|
251
|
+
for y in known_years:
|
252
|
+
o.set_year(y)
|
253
|
+
if np.isnan(getattr(o, param)).any():
|
254
|
+
msg = 'param:<{}>; not found in year={}'
|
255
|
+
fail = msg.format(param, y)
|
256
|
+
failures += fail + '\n'
|
257
|
+
if failures:
|
258
|
+
raise ValueError(failures)
|
259
|
+
|
260
|
+
|
261
|
+
@pytest.mark.parametrize("jfname, pfname",
|
262
|
+
[("consumption.json", "consumption.py"),
|
263
|
+
("policy_current_law.json", "calcfunctions.py"),
|
264
|
+
("growdiff.json", "growdiff.py")])
|
265
|
+
def test_parameters_mentioned(tests_path, jfname, pfname):
|
266
|
+
"""
|
267
|
+
Make sure each JSON parameter is mentioned in PYTHON code file.
|
268
|
+
"""
|
269
|
+
# read JSON parameter file into a dictionary
|
270
|
+
path = os.path.join(tests_path, '..', jfname)
|
271
|
+
pfile = open(path, 'r')
|
272
|
+
allparams = json.load(pfile)
|
273
|
+
pfile.close()
|
274
|
+
assert isinstance(allparams, dict)
|
275
|
+
# read PYTHON code file text
|
276
|
+
if pfname == 'consumption.py':
|
277
|
+
# consumption.py does not explicitly name the parameters
|
278
|
+
code_text = ''
|
279
|
+
for var in Consumption.RESPONSE_VARS:
|
280
|
+
code_text += 'MPC_{}\n'.format(var)
|
281
|
+
for var in Consumption.BENEFIT_VARS:
|
282
|
+
code_text += 'BEN_{}_value\n'.format(var)
|
283
|
+
elif pfname == 'growdiff.py':
|
284
|
+
# growdiff.py does not explicitly name the parameters
|
285
|
+
code_text = ''
|
286
|
+
for var in GrowFactors.VALID_NAMES:
|
287
|
+
code_text += '{}\n'.format(var)
|
288
|
+
else:
|
289
|
+
# parameters are explicitly named in PYTHON file
|
290
|
+
path = os.path.join(tests_path, '..', pfname)
|
291
|
+
pfile = open(path, 'r')
|
292
|
+
code_text = pfile.read()
|
293
|
+
pfile.close()
|
294
|
+
# check that each param (without leading _) is mentioned in code text
|
295
|
+
for pname in allparams:
|
296
|
+
if pname == "schema":
|
297
|
+
continue
|
298
|
+
assert pname[1:] in code_text
|
299
|
+
|
300
|
+
|
301
|
+
# following tests access private methods, so pylint: disable=protected-access
|
302
|
+
|
303
|
+
class ArrayParams(Parameters):
|
304
|
+
defaults = {
|
305
|
+
"schema": {
|
306
|
+
"labels": {
|
307
|
+
"year": {
|
308
|
+
"type": "int",
|
309
|
+
"validators": {"range": {"min": 2013, "max": 2028}}
|
310
|
+
},
|
311
|
+
"MARS": {
|
312
|
+
"type": "str",
|
313
|
+
"validators": {
|
314
|
+
"choice": {
|
315
|
+
"choices": [
|
316
|
+
"single",
|
317
|
+
"joint",
|
318
|
+
"mseparate",
|
319
|
+
"headhh",
|
320
|
+
"widow",
|
321
|
+
# test value of II_brk2 has 6 columns
|
322
|
+
"extra",
|
323
|
+
]
|
324
|
+
}
|
325
|
+
}
|
326
|
+
},
|
327
|
+
"idedtype": {
|
328
|
+
"type": "str",
|
329
|
+
"validators": {
|
330
|
+
"choice": {"choices": ["med", "sltx", "retx"]}
|
331
|
+
}
|
332
|
+
}
|
333
|
+
},
|
334
|
+
"additional_members": {
|
335
|
+
"indexable": {
|
336
|
+
"type": "bool"
|
337
|
+
},
|
338
|
+
"indexed": {
|
339
|
+
"type": "bool"
|
340
|
+
},
|
341
|
+
},
|
342
|
+
"operators": {
|
343
|
+
"array_first": True,
|
344
|
+
"label_to_extend": "year"
|
345
|
+
}
|
346
|
+
},
|
347
|
+
"one_dim": {
|
348
|
+
"title": "One dimension parameter",
|
349
|
+
"description": "",
|
350
|
+
"type": "float",
|
351
|
+
"indexed": True,
|
352
|
+
"indexable": True,
|
353
|
+
"value": [{"year": 2013, "value": 5}]
|
354
|
+
},
|
355
|
+
"two_dim": {
|
356
|
+
"title": "Two dimension parameter",
|
357
|
+
"description": "",
|
358
|
+
"type": "float",
|
359
|
+
"indexed": True,
|
360
|
+
"indexable": True,
|
361
|
+
"value": [
|
362
|
+
{"year": 2013, "idedtype": "med", "value": 1},
|
363
|
+
{"year": 2013, "idedtype": "sltx", "value": 2},
|
364
|
+
{"year": 2013, "idedtype": "retx", "value": 3}
|
365
|
+
]
|
366
|
+
},
|
367
|
+
"II_brk2": {
|
368
|
+
"title": "II_brk2",
|
369
|
+
"description": "",
|
370
|
+
"type": "float",
|
371
|
+
"indexed": True,
|
372
|
+
"indexable": True,
|
373
|
+
"value": [
|
374
|
+
{"year": 2013, "MARS": "single", "value": 1},
|
375
|
+
{"year": 2013, "MARS": "joint", "value": 2},
|
376
|
+
{"year": 2013, "MARS": "mseparate", "value": 3},
|
377
|
+
{"year": 2013, "MARS": "headhh", "value": 2},
|
378
|
+
{"year": 2013, "MARS": "widow", "value": 3},
|
379
|
+
{"year": 2013, "MARS": "extra", "value": 3},
|
380
|
+
]
|
381
|
+
}
|
382
|
+
}
|
383
|
+
|
384
|
+
# These will be controlled directly through the extend method.
|
385
|
+
label_to_extend = None
|
386
|
+
array_first = False
|
387
|
+
|
388
|
+
START_YEAR = 2013
|
389
|
+
LAST_YEAR = 2034
|
390
|
+
NUM_YEARS = LAST_YEAR - START_YEAR + 1
|
391
|
+
|
392
|
+
def __init__(self, **kwargs):
|
393
|
+
super().__init__(
|
394
|
+
ArrayParams.START_YEAR,
|
395
|
+
ArrayParams.NUM_YEARS,
|
396
|
+
**kwargs
|
397
|
+
)
|
398
|
+
self._inflation_rates = [0.02] * self.num_years
|
399
|
+
self._wage_growth_rates = [0.03] * self.num_years
|
400
|
+
|
401
|
+
def update_params(self, revision,
|
402
|
+
print_warnings=True, raise_errors=True):
|
403
|
+
"""
|
404
|
+
Update parameters given specified revision dictionary.
|
405
|
+
"""
|
406
|
+
self._update(revision, print_warnings, raise_errors)
|
407
|
+
|
408
|
+
def set_rates(self):
|
409
|
+
pass
|
410
|
+
|
411
|
+
|
412
|
+
def test_expand_xd_errors():
|
413
|
+
"""
|
414
|
+
One of several _expand_?D tests.
|
415
|
+
"""
|
416
|
+
params = ArrayParams(label_to_extend=None, array_first=False)
|
417
|
+
with pytest.raises(paramtools.ValidationError):
|
418
|
+
params.extend(label="year", label_values=[1, 2, 3])
|
419
|
+
|
420
|
+
|
421
|
+
def test_expand_empty():
|
422
|
+
params = ArrayParams(label_to_extend=None, array_first=False)
|
423
|
+
params.sort_values()
|
424
|
+
one_dim = copy.deepcopy(params.one_dim)
|
425
|
+
|
426
|
+
params.extend(label="year", label_values=[])
|
427
|
+
|
428
|
+
params.sort_values()
|
429
|
+
assert params.one_dim == one_dim
|
430
|
+
|
431
|
+
|
432
|
+
def test_expand_1d_scalar():
|
433
|
+
yrs = 12
|
434
|
+
val = 10.0
|
435
|
+
exp = np.array([val * math.pow(1.02, i) for i in range(0, yrs)])
|
436
|
+
|
437
|
+
yrslist = list(range(2013, 2013 + 12))
|
438
|
+
params = ArrayParams(label_to_extend=None, array_first=False)
|
439
|
+
params.adjust({"one_dim": val})
|
440
|
+
params.extend(params=["one_dim"], label="year", label_values=yrslist)
|
441
|
+
res = params.to_array("one_dim", year=yrslist)
|
442
|
+
assert np.allclose(exp, res, atol=0.01, rtol=0.0)
|
443
|
+
|
444
|
+
params = ArrayParams(label_to_extend=None, array_first=False)
|
445
|
+
params.adjust({"one_dim": val})
|
446
|
+
params.extend(params=["one_dim"], label="year", label_values=[2013])
|
447
|
+
res = params.to_array("one_dim", year=2013)
|
448
|
+
assert np.allclose(np.array([val]), res, atol=0.01, rtol=0.0)
|
449
|
+
|
450
|
+
|
451
|
+
def test_expand_2d_short_array():
|
452
|
+
"""
|
453
|
+
One of several _expand_?D tests.
|
454
|
+
"""
|
455
|
+
ary = np.array([[1., 2., 3.]])
|
456
|
+
val = np.array([1., 2., 3.])
|
457
|
+
exp2 = np.array([val * math.pow(1.02, i) for i in range(1, 5)])
|
458
|
+
exp1 = np.array([1., 2., 3.])
|
459
|
+
exp = np.zeros((5, 3))
|
460
|
+
exp[:1] = exp1
|
461
|
+
exp[1:] = exp2
|
462
|
+
|
463
|
+
params = ArrayParams(array_first=False, label_to_extend=None)
|
464
|
+
years = [2013, 2014, 2015, 2016, 2017]
|
465
|
+
params.extend(
|
466
|
+
params=["two_dim"],
|
467
|
+
label="year",
|
468
|
+
label_values=years,
|
469
|
+
)
|
470
|
+
res = params.to_array("two_dim", year=years)
|
471
|
+
assert np.allclose(exp, res, atol=0.01, rtol=0.0)
|
472
|
+
|
473
|
+
|
474
|
+
def test_expand_2d_variable_rates():
|
475
|
+
"""
|
476
|
+
One of several _expand_?D tests.
|
477
|
+
"""
|
478
|
+
ary = np.array([[1., 2., 3.]])
|
479
|
+
cur = np.array([1., 2., 3.])
|
480
|
+
irates = [0.02, 0.02, 0.02, 0.03, 0.035]
|
481
|
+
exp2 = []
|
482
|
+
for i in range(0, 4):
|
483
|
+
idx = i + len(ary) - 1
|
484
|
+
cur = np.array(cur * (1.0 + irates[idx]))
|
485
|
+
print('cur is ', cur)
|
486
|
+
exp2.append(cur)
|
487
|
+
exp1 = np.array([1., 2., 3.])
|
488
|
+
exp = np.zeros((5, 3))
|
489
|
+
exp[:1] = exp1
|
490
|
+
exp[1:] = exp2
|
491
|
+
|
492
|
+
params = ArrayParams(array_first=False, label_to_extend=None)
|
493
|
+
params._inflation_rates = irates
|
494
|
+
years = [2013, 2014, 2015, 2016, 2017]
|
495
|
+
params.extend(params=["two_dim"], label="year", label_values=years)
|
496
|
+
res = params.to_array("two_dim", year=years)
|
497
|
+
assert np.allclose(exp, res, atol=0.01, rtol=0.0)
|
498
|
+
|
499
|
+
|
500
|
+
def test_expand_2d_already_filled():
|
501
|
+
"""
|
502
|
+
One of several _expand_?D tests.
|
503
|
+
"""
|
504
|
+
# pylint doesn't like caps in var name, so pylint: disable=invalid-name
|
505
|
+
_II_brk2 = [[36000., 72250., 36500., 48600., 72500., 36250.],
|
506
|
+
[38000., 74000., 36900., 49400., 73800., 36900.],
|
507
|
+
[40000., 74900., 37450., 50200., 74900., 37450.]]
|
508
|
+
|
509
|
+
years = [2013, 2014, 2015]
|
510
|
+
params = ArrayParams(
|
511
|
+
array_first=False,
|
512
|
+
label_to_extend=None,
|
513
|
+
)
|
514
|
+
params.adjust({
|
515
|
+
"II_brk2": params.from_array("II_brk2", np.array(_II_brk2), year=years)
|
516
|
+
})
|
517
|
+
|
518
|
+
params.extend(
|
519
|
+
params=["II_brk2"], label="year", label_values=years
|
520
|
+
)
|
521
|
+
res = params.to_array("II_brk2", year=years)
|
522
|
+
assert np.allclose(res, np.array(_II_brk2), atol=0.01, rtol=0.0)
|
523
|
+
|
524
|
+
|
525
|
+
def test_expand_2d_partial_expand():
|
526
|
+
"""
|
527
|
+
One of several _expand_?D tests.
|
528
|
+
"""
|
529
|
+
# pylint doesn't like caps in var name, so pylint: disable=invalid-name
|
530
|
+
_II_brk2 = [[36000.0, 72250.0, 36500.0, 48600.0, 72500.0, 36250.0],
|
531
|
+
[38000.0, 74000.0, 36900.0, 49400.0, 73800.0, 36900.0],
|
532
|
+
[40000.0, 74900.0, 37450.0, 50200.0, 74900.0, 37450.0]]
|
533
|
+
# We have three years worth of data, need 4 years worth,
|
534
|
+
# but we only need the inflation rate for year 3 to go
|
535
|
+
# from year 3 -> year 4
|
536
|
+
inf_rates = [0.02, 0.02, 0.03]
|
537
|
+
exp1 = 40000. * 1.03
|
538
|
+
exp2 = 74900. * 1.03
|
539
|
+
exp3 = 37450. * 1.03
|
540
|
+
exp4 = 50200. * 1.03
|
541
|
+
exp5 = 74900. * 1.03
|
542
|
+
exp6 = 37450. * 1.03
|
543
|
+
exp = [[36000.0, 72250.0, 36500.0, 48600.0, 72500.0, 36250.0],
|
544
|
+
[38000.0, 74000.0, 36900.0, 49400.0, 73800.0, 36900.0],
|
545
|
+
[40000.0, 74900.0, 37450.0, 50200.0, 74900.0, 37450.0],
|
546
|
+
[exp1, exp2, exp3, exp4, exp5, exp6]]
|
547
|
+
|
548
|
+
years = [2013, 2014, 2015]
|
549
|
+
params = ArrayParams(array_first=False, label_to_extend=None)
|
550
|
+
params.adjust({
|
551
|
+
"II_brk2": params.from_array(
|
552
|
+
"II_brk2",
|
553
|
+
np.array(_II_brk2),
|
554
|
+
year=years
|
555
|
+
)
|
556
|
+
})
|
557
|
+
params._inflation_rates[:3] = inf_rates
|
558
|
+
params.extend(
|
559
|
+
params=["II_brk2"], label="year", label_values=years + [2016]
|
560
|
+
)
|
561
|
+
res = params.to_array("II_brk2", year=years + [2016])
|
562
|
+
assert np.allclose(res, exp, atol=0.01, rtol=0.0)
|
563
|
+
|
564
|
+
|
565
|
+
taxcalc_revision = """
|
566
|
+
{
|
567
|
+
"consumption": {"BEN_mcaid_value": {"2013": 0.9}}
|
568
|
+
}
|
569
|
+
"""
|
570
|
+
|
571
|
+
paramtools_revision = """
|
572
|
+
{
|
573
|
+
"consumption": {"BEN_mcaid_value": [{"year": "2013", "value": 0.9}]}
|
574
|
+
}
|
575
|
+
"""
|
576
|
+
|
577
|
+
paramtools_revision2 = """
|
578
|
+
{
|
579
|
+
"consumption": {"BEN_mcaid_value": 0.9}
|
580
|
+
}
|
581
|
+
"""
|
582
|
+
|
583
|
+
|
584
|
+
@pytest.mark.parametrize("good_revision", [
|
585
|
+
taxcalc_revision,
|
586
|
+
paramtools_revision,
|
587
|
+
])
|
588
|
+
def test_read_json_revision(good_revision):
|
589
|
+
"""
|
590
|
+
Check _read_json_revision logic.
|
591
|
+
"""
|
592
|
+
# pllint: disable=private-method
|
593
|
+
with pytest.raises(TypeError):
|
594
|
+
# error because first obj argument is neither None nor a string
|
595
|
+
Parameters._read_json_revision(list(), '')
|
596
|
+
with pytest.raises(ValueError):
|
597
|
+
# error because second topkey argument must be a string
|
598
|
+
Parameters._read_json_revision(good_revision, 999)
|
599
|
+
with pytest.raises(ValueError):
|
600
|
+
# error because second topkey argument is not in good_revision
|
601
|
+
Parameters._read_json_revision(good_revision, 'unknown_topkey')
|
602
|
+
|
603
|
+
|
604
|
+
@pytest.mark.parametrize("params,is_paramtools", [
|
605
|
+
(taxcalc_revision, False),
|
606
|
+
(paramtools_revision, True),
|
607
|
+
(paramtools_revision2, True),
|
608
|
+
])
|
609
|
+
def test_read_json_revision_foramts(params, is_paramtools):
|
610
|
+
"""
|
611
|
+
Check _read_json_revision for ParamTools and Tax-Calculator
|
612
|
+
styled parameters.
|
613
|
+
"""
|
614
|
+
result = Parameters._read_json_revision(params, "consumption")
|
615
|
+
assert is_paramtools_format(result) is is_paramtools
|
616
|
+
if is_paramtools:
|
617
|
+
assert result == json.loads(params)["consumption"]
|