PSLmodels / Tax-Brain

Tax-Brain is an integrator model for PSL tax models
http://taxbrain.pslmodels.org/
MIT License
9 stars 14 forks source link

CS Error #145

Closed hdoupe closed 1 week ago

hdoupe commented 3 years ago

I noticed an error when running a reform like this one:

adjustment = {
    "behavior": {},
    "policy": {
        "II_brk7_checkbox": [{"value": True}],
        "II_brk7": [{"value": 445400, "MARS": "single", "year": 2020}],
        "II_brk6": [{"value": 316700, "MARS": "single", "year": 2020}],
    },
}

This causes a py.test failure (reproducible here: https://github.com/hdoupe/Tax-Brain/tree/cs-error-repro):

✗  py.test cs-config/cs_config/tests/test_functions.py::TestFunctions1::test_validate_inputs -v
==================================== test session starts =====================================
platform linux -- Python 3.8.6, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /home/hankdoupe/miniconda3/envs/taxbrain-dev/bin/python
cachedir: .pytest_cache
rootdir: /home/hankdoupe/Tax-Brain/cs-config
collected 1 item                                                                             

cs-config/cs_config/tests/test_functions.py::TestFunctions1::test_validate_inputs FAILED [100%]

========================================== FAILURES ==========================================
____________________________ TestFunctions1.test_validate_inputs _____________________________

self = <cs_config.tests.test_functions.TestFunctions1 object at 0x7f84257a5370>

    def test_validate_inputs(self):
        self.test_all_data_specified()
        inputs = self.get_inputs({})
        check_get_inputs(inputs)

        init_metaparams = inputs["meta_parameters"]
        init_modparams = inputs["model_parameters"]

        class MetaParams(Parameters):
            array_first = True
            defaults = init_metaparams

        mp_spec = MetaParams().specification(serializable=True)

        ew_template = {
            major_sect: {"errors": {}, "warnings": {}} for major_sect in init_modparams
        }
        ew_schema = ErrorsWarnings()

        valid_res = self.validate_inputs(
            mp_spec, self.ok_adjustment, copy.deepcopy(ew_template)
        )
        check_validate_inputs(valid_res)
        for major_sect, ew_dict in valid_res["errors_warnings"].items():
            ew_schema.load(ew_dict)
            if len(ew_dict.get("errors")) > 0:
                raise CSKitError(
                    f"Expected section {major_sect} to be valid but it has errors:\n"
                    f"{ew_dict}"
                )

        if valid_res.get("custom_adjustment"):
            try:
                json.dumps(valid_res["custom_adjustment"])
            except TypeError as e:
                raise SerializationError(
                    f"Parameters must be JSON serializable: \n\n\t{str(e)}\n"
                )

>       invalid_res = self.validate_inputs(
            mp_spec, self.bad_adjustment, copy.deepcopy(ew_template)
        )

../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/cs_kit/validate.py:194: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
cs-config/cs_config/functions.py:73: in validate_inputs
    policy_params.adjust(pol_params, raise_errors=False, ignore_warnings=True)
../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/taxcalc/parameters.py:135: in adjust
    raise ve
../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/taxcalc/parameters.py:132: in adjust
    return self.adjust_with_indexing(params_or_path, **kwargs)
../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/taxcalc/parameters.py:421: in adjust_with_indexing
    self.extend(params=[base_param], label="year")
../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/paramtools/parameters.py:871: in extend
    self._adjust(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <taxcalc.policy.Policy object at 0x7f83b69e4b80>
params_or_path = defaultdict(<class 'list'>, {'II_brk7': [OrderedDict([('year', 2013), ('MARS', 'single'), ('value', 9e+99), ('_auto', ...lue', 9e+99), ('_auto', True)]), OrderedDict([('year', 2030), ('MARS', 'widow'), ('value', 9e+99), ('_auto', True)])]})
ignore_warnings = False, raise_errors = True, extend_adj = False, is_deserialized = True
clobber = True

    def _adjust(
        self,
        params_or_path,
        ignore_warnings=False,
        raise_errors=True,
        extend_adj=True,
        is_deserialized=False,
        clobber=True,
    ):
        """
        Internal method for performing adjustments.
        """
        # Validate user adjustments.
        if is_deserialized:
            parsed_params = {}
            try:
                parsed_params = self._validator_schema.load(
                    params_or_path, ignore_warnings, is_deserialized=True
                )
            except MarshmallowValidationError as ve:
                self._parse_validation_messages(ve.messages, params_or_path)
        else:
            params = self.read_params(params_or_path)
            parsed_params = {}
            try:
                parsed_params = self._validator_schema.load(
                    params, ignore_warnings
                )
            except MarshmallowValidationError as ve:
                self._parse_validation_messages(ve.messages, params)

        if not self._errors:
            if self.label_to_extend is not None and extend_adj:
                extend_grid = self._stateless_label_grid[self.label_to_extend]
                to_delete = defaultdict(list)
                backup = {}
                for param, vos in parsed_params.items():
                    for vo in utils.grid_sort(
                        vos, self.label_to_extend, extend_grid
                    ):

                        if self.label_to_extend in vo:
                            if clobber:
                                queryset = self.sel[param]
                            else:
                                queryset = self.sel[param]["_auto"] == True

                            queryset &= queryset.gt(
                                strict=False,
                                **{
                                    self.label_to_extend: vo[
                                        self.label_to_extend
                                    ]
                                },
                            )
                            other_labels = utils.filter_labels(
                                vo,
                                drop=[self.label_to_extend, "value", "_auto"],
                            )
                            if other_labels:
                                queryset &= intersection(
                                    queryset.eq(strict=False, **{label: value})
                                    for label, value in other_labels.items()
                                )

                            to_delete[param] += list(queryset)
                    # make copy of value objects since they
                    # are about to be modified
                    backup[param] = copy.deepcopy(self._data[param]["value"])
                try:
                    array_first = self.array_first
                    self.array_first = False

                    # delete params that will be overwritten out by extend.
                    self.delete(
                        to_delete,
                        extend_adj=False,
                        raise_errors=True,
                        ignore_warnings=ignore_warnings,
                    )

                    # set user adjustments.
                    self._adjust(
                        parsed_params,
                        extend_adj=False,
                        raise_errors=True,
                        ignore_warnings=ignore_warnings,
                    )
                    self.extend(
                        params=parsed_params.keys(),
                        ignore_warnings=ignore_warnings,
                        raise_errors=True,
                    )
                except ValidationError:
                    for param in backup:
                        self._data[param]["value"] = backup[param]
                finally:
                    self.array_first = array_first
            else:
                for param, value in parsed_params.items():
                    self._update_param(param, value)

        self._validator_schema.context["spec"] = self

        has_errors = bool(self._errors.get("messages"))
        has_warnings = bool(self._warnings.get("messages"))
        # throw error if raise_errors is True or ignore_warnings is False
        if (raise_errors and has_errors) or (
            not ignore_warnings and has_warnings
        ):
>           raise self.validation_error
E           paramtools.exceptions.ValidationError: {
E               "errors": {
E                   "II_brk7": [
E                       "II_brk7[MARS=single, year=2020] 445400.0 < min 518158.62 II_brk6[MARS=single, year=2020]"
E                   ]
E               }
E           }

../miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/paramtools/parameters.py:373: ValidationError
====================================== warnings summary ======================================
cs_config/tests/test_functions.py::TestFunctions1::test_validate_inputs
  /home/hankdoupe/miniconda3/envs/taxbrain-dev/lib/python3.8/site-packages/paramtools/select.py:20: UserWarning: The select module is deprecated. Use Values instead.
    warnings.warn("The select module is deprecated. Use Values instead.")

-- Docs: https://docs.pytest.org/en/stable/warnings.html
================================== short test summary info ===================================
FAILED cs-config/cs_config/tests/test_functions.py::TestFunctions1::test_validate_inputs - ...
================================ 1 failed, 1 warning in 5.39s ================================
hdoupe commented 3 years ago

I think this is something I need to check in Tax-Calculator. I'll comment back in a few hours with more information.

hdoupe commented 3 years ago

This will be resolved once the new version of Tax-Calculator is released.