def input_to_json_data(value, state=None):
    return conv.pipe(
        conv.make_input_to_json(),
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                resultat_officiel=conv.pipe(
                    conv.test_isinstance(dict),
                    conv.uniform_mapping(
                        conv.pipe(
                            conv.test_isinstance(basestring),
                            conv.not_none,
                        ),
                        conv.pipe(
                            conv.test_isinstance(dict),
                            conv.struct(
                                dict(
                                    code=conv.pipe(
                                        conv.test_isinstance(basestring),
                                        conv.not_none,
                                    ),
                                    name=conv.pipe(
                                        conv.test_isinstance(basestring),
                                        conv.not_none,
                                    ),
                                    value=conv.pipe(
                                        conv.test_isinstance(float),
                                        conv.not_none,
                                    ),
                                ), ),
                            conv.not_none,
                        ),
                    ),
                    conv.not_none,
                ),
                scenario=conv.pipe(
                    conv.test_isinstance(dict),
                    conv.struct(
                        dict(
                            test_case=conv.pipe(
                                conv.test_isinstance(dict),
                                # For each entity convert its members from a dict to a list.
                                conv.uniform_mapping(
                                    conv.noop,
                                    conv.pipe(
                                        conv.test_isinstance(dict),
                                        conv.function(
                                            transform_entity_member_by_id_to_members
                                        ),
                                    ),
                                ),
                            ), ),
                        default=conv.noop,
                    ),
                    tax_benefit_system.Scenario.make_json_to_instance(
                        tax_benefit_system=tax_benefit_system),
                    conv.not_none,
                ),
            ), ),
    )(value, state=state or conv.default_state)
    def validate_node_json(node, state=None):
        if node is None:
            return None, None
        if state is None:
            state = conv.default_state
        validated_node, errors = conv.pipe(
            conv.condition(
                conv.test_isinstance(list),
                conv.uniform_sequence(
                    validate_node_json,
                    drop_none_items=True,
                ),
                conv.pipe(
                    conv.condition(
                        conv.test_isinstance(basestring_type),
                        conv.function(lambda code: dict(code=code)),
                        conv.test_isinstance(dict),
                    ),
                    conv.struct(
                        dict(
                            children=conv.pipe(
                                conv.test_isinstance(list),
                                conv.uniform_sequence(
                                    validate_node_json,
                                    drop_none_items=True,
                                ),
                                conv.empty_to_none,
                            ),
                            code=conv.pipe(
                                conv.test_isinstance(basestring_type),
                                conv.cleanup_line,
                            ),
                        ),
                        constructor=collections.OrderedDict,
                        default=conv.noop,
                        drop_none_values='missing',
                        keep_value_order=True,
                    ),
                ),
            ),
            conv.empty_to_none,
        )(node, state=state)
        if validated_node is None or errors is not None:
            return validated_node, errors

        if isinstance(validated_node,
                      dict) and not validated_node.get('children'):
            validated_node, errors = conv.struct(
                dict(code=conv.pipe(
                    conv.test_in(tax_benefit_system.variables),
                    conv.not_none,
                ), ),
                default=conv.noop,
            )(validated_node, state=state)
        return validated_node, errors
def input_to_json_data(value, state=None):
    return conv.pipe(
        conv.make_input_to_json(),
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                resultat_officiel=conv.pipe(
                    conv.test_isinstance(dict),
                    conv.uniform_mapping(
                        conv.pipe(conv.test_isinstance(basestring), conv.not_none),
                        conv.pipe(
                            conv.test_isinstance(dict),
                            conv.struct(
                                dict(
                                    code=conv.pipe(conv.test_isinstance(basestring), conv.not_none),
                                    name=conv.pipe(conv.test_isinstance(basestring), conv.not_none),
                                    value=conv.pipe(conv.test_isinstance(float), conv.not_none),
                                )
                            ),
                            conv.not_none,
                        ),
                    ),
                    conv.not_none,
                ),
                scenario=conv.pipe(
                    conv.test_isinstance(dict),
                    conv.struct(
                        dict(
                            test_case=conv.pipe(
                                conv.test_isinstance(dict),
                                # For each entity convert its members from a dict to a list.
                                conv.uniform_mapping(
                                    conv.noop,
                                    conv.pipe(
                                        conv.test_isinstance(dict),
                                        conv.function(transform_entity_member_by_id_to_members),
                                    ),
                                ),
                            )
                        ),
                        default=conv.noop,
                    ),
                    tax_benefit_system.Scenario.make_json_to_instance(tax_benefit_system=tax_benefit_system),
                    conv.not_none,
                ),
            )
        ),
    )(value, state=state or conv.default_state)
def validate_parameter_xml_json(parameter, state = None):
    if parameter is None:
        return None, None
    state = conv.add_ancestor_to_state(state, parameter)
    validated_parameter, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                code = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    conv.not_none,
                    ),
                description = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    ),
                format = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in(xml_json_formats),
                    ),
                tail = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                taille = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.test_in([
                        'moinsde20',
                        'plusde20',
                        ]),
                    ),
                text = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                type = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in(json_unit_by_xml_json_type),
                    ),
                VALUE = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_value_xml_json,
                        drop_none_items = True,
                        ),
                    make_validate_values_xml_json_dates(require_consecutive_dates = True),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(parameter, state = state)
    conv.remove_ancestor_from_state(state, parameter)
    return validated_parameter, errors
def validate_dated_legislation_json(dated_legislation_json, state = None):
    if dated_legislation_json is None:
        return None, None
    if state is None:
        state = conv.default_state

    dated_legislation_json, error = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                datesim = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                    ),
                ),
            constructor = collections.OrderedDict,
            default = conv.noop,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(dated_legislation_json, state = state)
    if error is not None:
        return dated_legislation_json, error

    datesim = dated_legislation_json.pop('datesim')
    dated_legislation_json, error = validate_dated_node_json(dated_legislation_json, state = state)
    dated_legislation_json['datesim'] = datesim
    return dated_legislation_json, error
def validate_dated_legislation_json(dated_legislation_json, state=None):
    if dated_legislation_json is None:
        return None, None
    if state is None:
        state = conv.default_state

    dated_legislation_json, error = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(datesim=conv.pipe(
                conv.test_isinstance(basestring),
                conv.iso8601_input_to_date,
                conv.date_to_iso8601_str,
                conv.not_none,
            ), ),
            constructor=collections.OrderedDict,
            default=conv.noop,
            drop_none_values='missing',
            keep_value_order=True,
        ),
    )(dated_legislation_json, state=state)
    if error is not None:
        return dated_legislation_json, error

    datesim = dated_legislation_json.pop('datesim')
    dated_legislation_json, error = validate_dated_node_json(
        dated_legislation_json, state=state)
    dated_legislation_json['datesim'] = datesim
    return dated_legislation_json, error
def validate_slice_json(slice, state = None):
    if slice is None:
        return None, None
    state = conv.add_ancestor_to_state(state, slice)
    validated_slice, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                base = validate_values_holder_json,
                comment = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                rate = conv.pipe(
                    validate_values_holder_json,
                    conv.not_none,
                    ),
                threshold = conv.pipe(
                    validate_values_holder_json,
                    conv.not_none,
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(slice, state = state)
    conv.remove_ancestor_from_state(state, slice)
    return validated_slice, errors
def validate_slice_json(slice, state=None):
    if slice is None:
        return None, None
    state = conv.add_ancestor_to_state(state, slice)
    validated_slice, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                base=validate_values_holder_json,
                comment=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                ),
                rate=conv.pipe(
                    validate_values_holder_json,
                    conv.not_none,
                ),
                threshold=conv.pipe(
                    validate_values_holder_json,
                    conv.not_none,
                ),
            ),
            constructor=collections.OrderedDict,
            drop_none_values='missing',
            keep_value_order=True,
        ),
    )(slice, state=state)
    conv.remove_ancestor_from_state(state, slice)
    return validated_slice, errors
    def json_or_python_to_test(value, state=None):
        if value is None:
            return value, None
        if state is None:
            state = conv.default_state
        value, error = conv.pipe(
            conv.test_isinstance(dict),
            validate,
        )(value, state=state)
        if error is not None:
            return value, error

        value, error = conv.struct(
            dict(output_variables=make_json_or_python_to_input_variables(
                tax_benefit_system, value['period']), ),
            default=conv.noop,
        )(value, state=state)
        if error is not None:
            return value, error

        test_case = value.copy()
        absolute_error_margin = test_case.pop('absolute_error_margin')
        axes = test_case.pop('axes')
        description = test_case.pop('description')
        input_variables = test_case.pop('input_variables')
        keywords = test_case.pop('keywords')
        name = test_case.pop('name')
        output_variables = test_case.pop('output_variables')
        period = test_case.pop('period')
        relative_error_margin = test_case.pop('relative_error_margin')

        if test_case is not None and all(
                entity_members is None
                for entity_members in test_case.values()):
            test_case = None

        scenario, error = tax_benefit_system.Scenario.make_json_to_instance(
            repair=True, tax_benefit_system=tax_benefit_system)(dict(
                axes=axes,
                input_variables=input_variables,
                period=period,
                test_case=test_case,
            ),
                                                                state=state)
        if error is not None:
            return scenario, error

        return {
            key: value
            for key, value in dict(
                absolute_error_margin=absolute_error_margin,
                description=description,
                keywords=keywords,
                name=name,
                output_variables=output_variables,
                relative_error_margin=relative_error_margin,
                scenario=scenario,
            ).items() if value is not None
        }, None
def validate_scale_xml_json(scale, state = None):
    if scale is None:
        return None, None
    state = conv.add_ancestor_to_state(state, scale)
    validated_scale, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                code = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    conv.not_none,
                    ),
                description = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    ),
                option = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in((
                        'contrib',
                        'main-d-oeuvre',
                        'noncontrib',
                        )),
                    ),
                TRANCHE = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_slice_xml_json,
                        drop_none_items = True,
                        ),
                    validate_slices_xml_json_dates,
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                tail = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                text = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                type = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in((
                        'monetary',
                        )),
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(scale, state = state)
    conv.remove_ancestor_from_state(state, scale)
    return validated_scale, errors
def make_json_or_python_to_axes(tax_benefit_system):
    column_by_name = tax_benefit_system.variables
    return conv.pipe(
        conv.test_isinstance(list),
        conv.uniform_sequence(
            conv.pipe(
                conv.item_or_sequence(
                    conv.pipe(
                        conv.test_isinstance(dict),
                        conv.struct(
                            dict(
                                count=conv.pipe(
                                    conv.test_isinstance(int),
                                    conv.test_greater_or_equal(1),
                                    conv.not_none,
                                ),
                                index=conv.pipe(
                                    conv.test_isinstance(int),
                                    conv.test_greater_or_equal(0),
                                    conv.default(0),
                                ),
                                max=conv.pipe(
                                    conv.test_isinstance((float, int)),
                                    conv.not_none,
                                ),
                                min=conv.pipe(
                                    conv.test_isinstance((float, int)),
                                    conv.not_none,
                                ),
                                name=conv.pipe(
                                    conv.test_isinstance(basestring_type),
                                    conv.test_in(column_by_name),
                                    conv.test(
                                        lambda column_name: tax_benefit_system.
                                        get_variable(column_name).dtype in
                                        (np.float32, np.int16, np.int32),
                                        error=N_(
                                            'Invalid type for axe: integer or float expected'
                                        )),
                                    conv.not_none,
                                ),
                                period=conv.function(periods.period),
                            ), ),
                    ),
                    drop_none_items=True,
                ),
                conv.make_item_to_singleton(),
            ),
            drop_none_items=True,
        ),
        conv.empty_to_none,
    )
Example #12
0
def check_entities_consistency(test_case, tax_benefit_system, state):
    """
        Checks that every person belongs to at most one entity of each kind
    """

    remaining_persons = {
        entity.plural: [
            person['id']
            for person in test_case[tax_benefit_system.person_entity.plural]
        ]
        for entity in tax_benefit_system.group_entities
    }

    def build_role_checker(role, entity):
        if role.max == 1:
            return role.key, conv.test_in_pop(remaining_persons[entity.plural])
        else:
            return role.plural, conv.uniform_sequence(
                conv.test_in_pop(remaining_persons[entity.plural]))

    entity_parsing_dict = {
        entity.plural: conv.uniform_sequence(
            conv.struct(
                dict(
                    build_role_checker(role, entity) for role in entity.roles),
                default=conv.noop,
            ), )
        for entity in tax_benefit_system.group_entities
    }

    test_case, error = conv.struct(
        entity_parsing_dict,
        default=conv.noop,
    )(test_case, state=state)

    return test_case, error, remaining_persons
def validate_dated_slice_json(slice, state = None):
    if slice is None:
        return None, None
    state = conv.add_ancestor_to_state(state, slice)
    validated_slice, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                base = conv.pipe(
                    validate_dated_value_json,
                    conv.test_greater_or_equal(0),
                    ),
                constant_amount = validate_dated_value_json,
                comment = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                rate = conv.pipe(
                    validate_dated_value_json,
                    conv.test_between(0, 1),
                    ),
                threshold = conv.pipe(
                    validate_dated_value_json,
                    conv.test_greater_or_equal(0),
                    ),
                 date = conv.pipe(
                    validate_dated_value_json,
                    conv.test_between(0, 1), #TODO: changer
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(slice, state = state)
    conv.remove_ancestor_from_state(state, slice)
    return validated_slice, errors
def validate_dated_slice_json(slice, state=None):
    if slice is None:
        return None, None
    state = conv.add_ancestor_to_state(state, slice)
    validated_slice, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                base=conv.pipe(
                    validate_dated_value_json,
                    conv.test_greater_or_equal(0),
                ),
                constant_amount=validate_dated_value_json,
                comment=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                ),
                rate=conv.pipe(
                    validate_dated_value_json,
                    conv.test_between(0, 1),
                ),
                threshold=conv.pipe(
                    validate_dated_value_json,
                    conv.test_greater_or_equal(0),
                ),
                date=conv.pipe(
                    validate_dated_value_json,
                    conv.test_between(0, 1),  #TODO: changer
                ),
            ),
            constructor=collections.OrderedDict,
            drop_none_values='missing',
            keep_value_order=True,
        ),
    )(slice, state=state)
    conv.remove_ancestor_from_state(state, slice)
    return validated_slice, errors
def validate_value_json(value, state = None):
    if value is None:
        return None, None
    container = state.ancestors[-1]
    value_converter = dict(
        boolean = conv.condition(
            conv.test_isinstance(int),
            conv.test_in((0, 1)),
            conv.test_isinstance(bool),
            ),
        float = conv.condition(
            conv.test_isinstance(int),
            conv.anything_to_float,
            conv.test_isinstance(float),
            ),
        integer = conv.condition(
            conv.test_isinstance(float),
            conv.pipe(
                conv.test(lambda number: round(number) == number),
                conv.function(int),
                ),
            conv.test_isinstance(int),
            ),
        rate = conv.condition(
            conv.test_isinstance(int),
            conv.anything_to_float,
            conv.test_isinstance(float),
            ),
        )[container.get('format') or 'float']  # Only parameters have a "format".
    state = conv.add_ancestor_to_state(state, value)
    validated_value, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            {
                u'comment': conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                u'from': conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                    ),
                u'to': conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                    ),
                u'value': conv.pipe(
                    value_converter,
                    conv.not_none,
                    ),
                },
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(value, state = state)
    conv.remove_ancestor_from_state(state, value)
    return validated_value, errors
    def post_process_test_case(self, test_case, period, state):

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
            }

        parents_id = set(
            parent_id
            for foyer_fiscal in test_case['foyers_fiscaux']
            for parent_id in foyer_fiscal['declarants']
            )
        test_case, error = conv.struct(
            dict(
                # foyers_fiscaux = conv.pipe(
                #     conv.uniform_sequence(
                #         conv.struct(
                #             dict(
                #                 enfants = conv.uniform_sequence(
                #                     conv.test(
                #                         lambda individu_id:
                #                             individu_by_id[individu_id].get('invalide', False)
                #                             or find_age(individu_by_id[individu_id], period.start.date,
                #                                 default = 0) <= 25,
                #                         error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                #                                 u" 25 ans ou être handicapée",
                #                         ),
                #                     ),
                #                 parents = conv.pipe(
                #                     conv.empty_to_none,
                #                     conv.not_none,
                #                     conv.test(lambda parents: len(parents) <= 2,
                #                         error = N_(u'A "famille" must have at most 2 "parents"'))
                #                     ),
                #                 ),
                #             default = conv.noop,
                #             ),
                #         ),
                #     conv.empty_to_none,
                #     conv.not_none,
                #     ),
                foyers_fiscaux = conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                declarants = conv.pipe(
                                    conv.empty_to_none,
                                    conv.not_none,
                                    conv.test(
                                        lambda declarants: len(declarants) <= 2,
                                        error = N_(u'A "foyer_fiscal" must have at most 2 "declarants"'),
                                        ),
                                    conv.uniform_sequence(conv.pipe(
                                        )),
                                    ),
                                personnes_a_charge = conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                            individu_by_id[individu_id].get('handicap', False)
                                            or find_age(individu_by_id[individu_id], period.start.date,
                                                default = 0) <= 25,
                                        error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                                u" 25 ans ou être handicapée",
                                        ),
                                    ),
                                ),
                            default = conv.noop,
                            ),
                        ),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                menages = conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                personne_de_reference = conv.not_none,
                                ),
                            default = conv.noop,
                            ),
                        ),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                ),
            default = conv.noop,
            )(test_case, state = state)

        return test_case, error
        # First validation and conversion step
        test_case, error = conv.pipe(
            conv.test_isinstance(dict),
            conv.struct(
                dict(
                    foyers_fiscaux = conv.pipe(
                        conv.make_item_to_singleton(),
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            conv.test_isinstance(dict),
                            drop_none_items = True,
                            ),
                        conv.uniform_sequence(
                            conv.struct(
                                dict(itertools.chain(
                                    dict(
                                        declarants = conv.pipe(
                                            conv.make_item_to_singleton(),
                                            conv.test_isinstance(list),
                                            conv.uniform_sequence(
                                                conv.test_isinstance((basestring, int)),
                                                drop_none_items = True,
                                                ),
                                            conv.default([]),
                                            ),
                                        id = conv.pipe(
                                            conv.test_isinstance((basestring, int)),
                                            conv.not_none,
                                            ),
                                        personnes_a_charge = conv.pipe(
                                            conv.make_item_to_singleton(),
                                            conv.test_isinstance(list),
                                            conv.uniform_sequence(
                                                conv.test_isinstance((basestring, int)),
                                                drop_none_items = True,
                                                ),
                                            conv.default([]),
                                            ),
                                        ).iteritems(),
                                    )),
                                drop_none_values = True,
                                ),
                            drop_none_items = True,
                            ),
                        conv.default([]),
                        ),
                    menages = conv.pipe(
                        conv.make_item_to_singleton(),
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            conv.test_isinstance(dict),
                            drop_none_items = True,
                            ),
                        conv.uniform_sequence(
                            conv.struct(
                                dict(itertools.chain(
                                    dict(
                                        autres = conv.pipe(
                                            # personnes ayant un lien autre avec la personne de référence
                                            conv.make_item_to_singleton(),
                                            conv.test_isinstance(list),
                                            conv.uniform_sequence(
                                                conv.test_isinstance((basestring, int)),
                                                drop_none_items = True,
                                                ),
                                            conv.default([]),
                                            ),
                                        # conjoint de la personne de référence
                                        conjoint = conv.test_isinstance((basestring, int)),
                                        enfants = conv.pipe(
                                            # enfants de la personne de référence ou de son conjoint
                                            conv.make_item_to_singleton(),
                                            conv.test_isinstance(list),
                                            conv.uniform_sequence(
                                                conv.test_isinstance((basestring, int)),
                                                drop_none_items = True,
                                                ),
                                            conv.default([]),
                                            ),
                                        id = conv.pipe(
                                            conv.test_isinstance((basestring, int)),
                                            conv.not_none,
                                            ),
                                        personne_de_reference = conv.test_isinstance((basestring, int)),
                                        ).iteritems(),
                                    )),
                                drop_none_values = True,
                                ),
                            drop_none_items = True,
                            ),
                        conv.default([]),
                        ),
                    ),
                ),
            )(test_case, state = state)

        # test_case, error = conv.struct(
        #     dict(
        #         foyers_fiscaux = conv.uniform_sequence(
        #             conv.struct(
        #                 dict(
        #                     declarants = conv.uniform_sequence(conv.test_in_pop(foyers_fiscaux_individus_id)),
        #                     personnes_a_charge = conv.uniform_sequence(conv.test_in_pop(
        #                         foyers_fiscaux_individus_id)),
        #                     ),
        #                 default = conv.noop,
        #                 ),
        #             ),
        #         menages = conv.uniform_sequence(
        #             conv.struct(
        #                 dict(
        #                     autres = conv.uniform_sequence(conv.test_in_pop(menages_individus_id)),
        #                     conjoint = conv.test_in_pop(menages_individus_id),
        #                     enfants = conv.uniform_sequence(conv.test_in_pop(menages_individus_id)),
        #                     personne_de_reference = conv.test_in_pop(menages_individus_id),
        #                     ),
        #                 default = conv.noop,
        #                 ),
        #             ),
        #         ),
        #     default = conv.noop,
        #     )(test_case, state = state)

        return test_case, error
def validate_node_json(node, state = None):
    if node is None:
        return None, None
    state = conv.add_ancestor_to_state(state, node)

    validated_node, error = conv.test_isinstance(dict)(node, state = state)
    if error is not None:
        conv.remove_ancestor_from_state(state, node)
        return validated_node, error

    validated_node, errors = conv.struct(
        {
            '@context': conv.pipe(
                conv.test_isinstance(basestring),
                conv.make_input_to_url(full = True),
                conv.test_equals(u'http://openfisca.fr/contexts/legislation.jsonld'),
                ),
            '@type': conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_line,
                conv.test_in((u'Node', u'Parameter', u'Scale')),
                conv.not_none,
                ),
            'comment': conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_text,
                ),
            'description': conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_line,
                ),
            },
        constructor = collections.OrderedDict,
        default = conv.noop,
        drop_none_values = 'missing',
        keep_value_order = True,
        )(validated_node, state = state)
    if errors is not None:
        conv.remove_ancestor_from_state(state, node)
        return validated_node, errors

    validated_node.pop('@context', None)  # Remove optional @context everywhere. It will be added to root node later.
    node_converters = {
        '@type': conv.noop,
        'comment': conv.noop,
        'description': conv.noop,
        }
    node_type = validated_node['@type']
    if node_type == u'Node':
        node_converters.update(dict(
            children = conv.pipe(
                conv.test_isinstance(dict),
                conv.uniform_mapping(
                    conv.pipe(
                        conv.test_isinstance(basestring),
                        conv.cleanup_line,
                        conv.not_none,
                        ),
                    conv.pipe(
                        validate_node_json,
                        conv.not_none,
                        ),
                    ),
                conv.empty_to_none,
                conv.not_none,
                ),
            ))
    elif node_type == u'Parameter':
        node_converters.update(dict(
            format = conv.pipe(
                conv.test_isinstance(basestring),
                conv.input_to_slug,
                conv.test_in([
                    'boolean',
                    'float',
                    'integer',
                    'rate',
                    ]),
                ),
            unit = conv.pipe(
                conv.test_isinstance(basestring),
                conv.input_to_slug,
                conv.test_in(units),
                ),
            values = conv.pipe(
                conv.test_isinstance(list),
                conv.uniform_sequence(
                    validate_value_json,
                    drop_none_items = True,
                    ),
                make_validate_values_json_dates(require_consecutive_dates = True),
                conv.empty_to_none,
                conv.not_none,
                ),
            ))
    else:
        assert node_type == u'Scale'
        node_converters.update(dict(
            option = conv.pipe(
                conv.test_isinstance(basestring),
                conv.input_to_slug,
                conv.test_in((
                    'contrib',
                    'main-d-oeuvre',
                    'noncontrib',
                    )),
                ),
            slices = conv.pipe(
                conv.test_isinstance(list),
                conv.uniform_sequence(
                    validate_slice_json,
                    drop_none_items = True,
                    ),
                validate_slices_json_dates,
                conv.empty_to_none,
                conv.not_none,
                ),
            unit = conv.pipe(
                conv.test_isinstance(basestring),
                conv.input_to_slug,
                conv.test_in((
                    'currency',
                    )),
                ),
            ))
    validated_node, errors = conv.struct(
        node_converters,
        constructor = collections.OrderedDict,
        drop_none_values = 'missing',
        keep_value_order = True,
        )(validated_node, state = state)

    conv.remove_ancestor_from_state(state, node)
    return validated_node, errors
Example #18
0
    def post_process_test_case(self, test_case, period, state):

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
        }

        parents_id = set(parent_id
                         for foyer_fiscal in test_case['foyers_fiscaux']
                         for parent_id in foyer_fiscal['declarants'])
        test_case, error = conv.struct(
            dict(
                # foyers_fiscaux = conv.pipe(
                #     conv.uniform_sequence(
                #         conv.struct(
                #             dict(
                #                 enfants = conv.uniform_sequence(
                #                     conv.test(
                #                         lambda individu_id:
                #                             individu_by_id[individu_id].get('invalide', False)
                #                             or find_age(individu_by_id[individu_id], period.start.date,
                #                                 default = 0) <= 25,
                #                         error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                #                                 u" 25 ans ou être handicapée",
                #                         ),
                #                     ),
                #                 parents = conv.pipe(
                #                     conv.empty_to_none,
                #                     conv.not_none,
                #                     conv.test(lambda parents: len(parents) <= 2,
                #                         error = N_(u'A "famille" must have at most 2 "parents"'))
                #                     ),
                #                 ),
                #             default = conv.noop,
                #             ),
                #         ),
                #     conv.empty_to_none,
                #     conv.not_none,
                #     ),
                foyers_fiscaux=conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                declarants=conv.pipe(
                                    conv.empty_to_none,
                                    conv.not_none,
                                    conv.test(
                                        lambda declarants: len(declarants) <=
                                        2,
                                        error=N_(
                                            u'A "foyer_fiscal" must have at most 2 "declarants"'
                                        ),
                                    ),
                                ),
                                personnes_a_charge=conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                        individu_by_id[individu_id].get(
                                            'handicap', False) or find_age(
                                                individu_by_id[individu_id],
                                                period.start.date,
                                                default=0) <= 25,
                                        error=
                                        u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                        u" 25 ans ou être handicapée",
                                    ), ),
                            ),
                            default=conv.noop,
                        ), ),
                    conv.empty_to_none,
                    conv.not_none,
                ),
                menages=conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(personne_de_reference=conv.not_none, ),
                            default=conv.noop,
                        ), ),
                    conv.empty_to_none,
                    conv.not_none,
                ),
            ),
            default=conv.noop,
        )(test_case, state=state)

        return test_case, error
        # First validation and conversion step
        test_case, error = conv.pipe(
            conv.test_isinstance(dict),
            conv.struct(
                dict(
                    foyers_fiscaux=conv.pipe(
                        conv.make_item_to_singleton(),
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            conv.test_isinstance(dict),
                            drop_none_items=True,
                        ),
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    itertools.chain(
                                        dict(
                                            declarants=conv.pipe(
                                                conv.make_item_to_singleton(),
                                                conv.test_isinstance(list),
                                                conv.uniform_sequence(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    drop_none_items=True,
                                                ),
                                                conv.default([]),
                                            ),
                                            id=conv.pipe(
                                                conv.test_isinstance(
                                                    (basestring, int)),
                                                conv.not_none,
                                            ),
                                            personnes_a_charge=conv.pipe(
                                                conv.make_item_to_singleton(),
                                                conv.test_isinstance(list),
                                                conv.uniform_sequence(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    drop_none_items=True,
                                                ),
                                                conv.default([]),
                                            ),
                                        ).iteritems(), )),
                                drop_none_values=True,
                            ),
                            drop_none_items=True,
                        ),
                        conv.default([]),
                    ),
                    menages=conv.pipe(
                        conv.make_item_to_singleton(),
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            conv.test_isinstance(dict),
                            drop_none_items=True,
                        ),
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    itertools.chain(
                                        dict(
                                            autres=conv.pipe(
                                                # personnes ayant un lien autre avec la personne de référence
                                                conv.make_item_to_singleton(),
                                                conv.test_isinstance(list),
                                                conv.uniform_sequence(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    drop_none_items=True,
                                                ),
                                                conv.default([]),
                                            ),
                                            # conjoint de la personne de référence
                                            conjoint=conv.test_isinstance(
                                                (basestring, int)),
                                            enfants=conv.pipe(
                                                # enfants de la personne de référence ou de son conjoint
                                                conv.make_item_to_singleton(),
                                                conv.test_isinstance(list),
                                                conv.uniform_sequence(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    drop_none_items=True,
                                                ),
                                                conv.default([]),
                                            ),
                                            id=conv.pipe(
                                                conv.test_isinstance(
                                                    (basestring, int)),
                                                conv.not_none,
                                            ),
                                            personne_de_reference=conv.
                                            test_isinstance((basestring, int)),
                                        ).iteritems(), )),
                                drop_none_values=True,
                            ),
                            drop_none_items=True,
                        ),
                        conv.default([]),
                    ),
                ), ),
        )(test_case, state=state)

        return test_case, error
            keep_value_order = True,
            ),
        )(value, state = state)
    conv.remove_ancestor_from_state(state, value)
    return validated_value, errors


validate_values_holder_xml_json = conv.struct(
    dict(
        VALUE = conv.pipe(
            conv.test_isinstance(list),
            conv.uniform_sequence(
                validate_value_xml_json,
                drop_none_items = True,
                ),
            make_validate_values_xml_json_dates(require_consecutive_dates = False),
            conv.empty_to_none,
            conv.not_none,
            ),
        ),
    constructor = collections.OrderedDict,
    drop_none_values = 'missing',
    keep_value_order = True,
    )


def xml_legislation_to_json(xml_element, state = None):
    if xml_element is None:
        return None, None
    json_key, json_element = translate_xml_element_to_json_item(xml_element)
    if json_key != 'NODE':
Example #20
0
    def validate_node_xml_json(node, state=None):
        validated_node, errors = conv.pipe(
            conv.test_isinstance(dict),
            conv.struct(
                dict(
                    code=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_line,
                        conv.not_none,
                    ),
                    color=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.function(lambda colors: colors.split(',')),
                        conv.uniform_sequence(
                            conv.pipe(
                                conv.input_to_int,
                                conv.test_between(0, 255),
                                conv.not_none,
                            ), ),
                        conv.test(
                            lambda colors: len(colors) == 3,
                            error=N_('Wrong number of colors in triplet.')),
                        conv.function(lambda colors: ','.join(
                            to_unicode(color) for color in colors)),
                    ),
                    desc=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_line,
                        conv.not_none,
                    ),
                    NODE=conv.pipe(
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            validate_node_xml_json,
                            drop_none_items=True,
                        ),
                        conv.empty_to_none,
                    ),
                    shortname=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_line,
                        conv.not_none,
                    ),
                    tail=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_text,
                    ),
                    text=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_text,
                    ),
                    typevar=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.input_to_int,
                        conv.test_equals(2),
                    ),
                ),
                constructor=collections.OrderedDict,
                drop_none_values='missing',
                keep_value_order=True,
            ),
        )(node, state=state or conv.default_state)
        if errors is not None:
            return validated_node, errors

        if not validated_node.get('NODE'):
            validated_node, errors = conv.struct(
                dict(code=conv.test_in(tax_benefit_system.variables), ),
                default=conv.noop,
            )(validated_node, state=state)
        return validated_node, errors
    def attribute_groupless_persons_to_entities(self, test_case, period, groupless_individus):
        individus_without_menage = groupless_individus['menages']
        individus_without_foyer_fiscal = groupless_individus['foyers_fiscaux']

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
            }

        # Affecte à un foyer fiscal chaque individu qui n'appartient à aucun d'entre eux.
        new_foyer_fiscal = dict(
            declarants = [],
            personnes_a_charge = [],
            )
        new_foyer_fiscal_id = None
        for individu_id in individus_without_foyer_fiscal[:]:
            # Tente d'affecter l'individu à un foyer fiscal d'après son ménage.
            menage, menage_role = find_menage_and_role(test_case, individu_id)
            if menage_role == u'personne_de_reference':
                conjoint_id = menage[u'conjoint']
                if conjoint_id is not None:
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, conjoint_id)
                    if other_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 1:
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est personne de
                        # référence dans un ménage, qu'il y a un conjoint dans ce ménage et que ce
                        # conjoint est seul déclarant dans un foyer fiscal, alors ajoute l'individu comme
                        # autre déclarant de ce foyer fiscal.
                        foyer_fiscal[u'declarants'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
            elif menage_role == u'conjoint':
                personne_de_reference_id = menage[u'personne_de_reference']
                if personne_de_reference_id is not None:
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, personne_de_reference_id)
                    if other_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 1:
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est conjoint
                        # dans un ménage, qu'il y a une personne de référence dans ce ménage et que
                        # cette personne est seul déclarant dans un foyer fiscal, alors ajoute l'individu
                        # comme autre déclarant de ce foyer fiscal.
                        foyer_fiscal[u'declarants'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
            elif menage_role == u'enfants' and (
                    menage['personne_de_reference'] is not None or menage[u'conjoint'] is not None):
                for other_id in (menage['personne_de_reference'], menage[u'conjoint']):
                    if other_id is None:
                        continue
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(test_case, other_id)
                    if other_role == u'declarants':
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est enfant dans
                        # un ménage, qu'il y a une personne à charge ou un conjoint dans ce ménage et que
                        # celui-ci est déclarant dans un foyer fiscal, alors ajoute l'individu comme
                        # personne à charge de ce foyer fiscal.
                        foyer_fiscal[u'personnes_a_charge'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
                        break

            if individu_id in individus_without_foyer_fiscal:
                # L'individu n'est toujours pas affecté à un foyer fiscal.
                individu = individu_by_id[individu_id]
                age = find_age(individu, period.start.date)
                if len(new_foyer_fiscal[u'declarants']) < 2 and (age is None or age >= 18):
                    new_foyer_fiscal[u'declarants'].append(individu_id)
                else:
                    new_foyer_fiscal[u'personnes_a_charge'].append(individu_id)
                if new_foyer_fiscal_id is None:
                    new_foyer_fiscal[u'id'] = new_foyer_fiscal_id = unicode(uuid.uuid4())
                    test_case[u'foyers_fiscaux'].append(new_foyer_fiscal)
                individus_without_foyer_fiscal.remove(individu_id)

            # Affecte à un ménage chaque individu qui n'appartient à aucun d'entre eux.
            new_menage = dict(
                autres = [],
                conjoint = None,
                enfants = [],
                personne_de_reference = None,
                )
            new_menage_id = None
            for individu_id in menages_individus_id[:]:
                # Tente d'affecter l'individu à un ménage d'après son foyer fiscal.
                foyer_fiscal, foyer_fiscal_role = find_foyer_fiscal_and_role(test_case, individu_id)
                if foyer_fiscal_role == u'declarants' and len(foyer_fiscal[u'declarants']) == 2:
                    for declarant_id in foyer_fiscal[u'declarants']:
                        if declarant_id != individu_id:
                            menage, other_role = find_menage_and_role(test_case, declarant_id)
                            if other_role == u'personne_de_reference' and menage[u'conjoint'] is None:
                                # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                # dans un foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et que
                                # cet autre déclarant est personne de référence dans un ménage et qu'il n'y a
                                # pas de conjoint dans ce ménage, alors ajoute l'individu comme conjoint de ce
                                # ménage.
                                menage[u'conjoint'] = individu_id
                                menages_individus_id.remove(individu_id)
                            elif other_role == u'conjoint' and menage[u'personne_de_reference'] is None:
                                # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                # dans une foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et
                                # que cet autre déclarant est conjoint dans un ménage et qu'il n'y a pas de
                                # personne de référence dans ce ménage, alors ajoute l'individu comme personne
                                # de référence de ce ménage.
                                menage[u'personne_de_reference'] = individu_id
                                menages_individus_id.remove(individu_id)
                            break
                elif foyer_fiscal_role == u'personnes_a_charge' and foyer_fiscal[u'declarants']:
                    for declarant_id in foyer_fiscal[u'declarants']:
                        menage, other_role = find_menage_and_role(test_case, declarant_id)
                        if other_role in (u'personne_de_reference', u'conjoint'):
                            # Quand l'individu n'est pas encore dans un ménage, mais qu'il est personne à charge
                            # dans un foyer fiscal, qu'il y a un déclarant dans ce foyer fiscal et que ce
                            # déclarant est personne de référence ou conjoint dans un ménage, alors ajoute
                            # l'individu comme enfant de ce ménage.
                            menage[u'enfants'].append(individu_id)
                            menages_individus_id.remove(individu_id)
                            break

                if individu_id in menages_individus_id:
                    # L'individu n'est toujours pas affecté à un ménage.
                    if new_menage[u'personne_de_reference'] is None:
                        new_menage[u'personne_de_reference'] = individu_id
                    elif new_menage[u'conjoint'] is None:
                        new_menage[u'conjoint'] = individu_id
                    else:
                        new_menage[u'enfants'].append(individu_id)
                    if new_menage_id is None:
                        new_menage[u'id'] = new_menage_id = unicode(uuid.uuid4())
                        test_case[u'menages'].append(new_menage)
                    menages_individus_id.remove(individu_id)

        remaining_individus_id = set(individus_without_foyer_fiscal).union(menages_individus_id)
        if remaining_individus_id:
            individu_index_by_id = {
                individu[u'id']: individu_index
                for individu_index, individu in enumerate(test_case[u'individus'])
                }
            if error is None:
                error = {}
            for individu_id in remaining_individus_id:
                error.setdefault('individus', {})[individu_index_by_id[individu_id]] = state._(
                    u"Individual is missing from {}").format(
                        state._(u' & ').join(
                            word
                            for word in [
                                u'foyers_fiscaux' if individu_id in individus_without_foyer_fiscal else None,
                                u'menages' if individu_id in menages_individus_id else None,
                                ]
                            if word is not None
                            ))
        if error is not None:
            return test_case, error

            # Third validation step
            individu_by_id = test_case['individus']
            test_case, error = conv.struct(
                dict(
                    foyers_fiscaux = conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    declarants = conv.pipe(
                                        conv.empty_to_none,
                                        conv.not_none,
                                        conv.test(
                                            lambda declarants: len(declarants) <= 2,
                                            error = N_(u'A "foyer_fiscal" must have at most 2 "declarants"'),
                                            ),
                                        # conv.uniform_sequence(conv.pipe(
                                            # conv.test(lambda individu_id:
                                            #     find_age(individu_by_id[individu_id], period.start.date,
                                            #         default = 100) >= 18,
                                            #     error = u"Un déclarant d'un foyer fiscal doit être agé d'au moins 18"
                                            #         u" ans",
                                            #     ),
                                            # conv.test(
                                                # lambda individu_id: individu_id in parents_id,
                                                # error = u"Un déclarant ou un conjoint sur la déclaration d'impôt, doit"
                                                        # u" être un parent dans sa famille",
                                                # ),
                                            # )),
                                        ),
                                    personnes_a_charge = conv.uniform_sequence(
                                        conv.test(
                                            lambda individu_id:
                                                individu_by_id[individu_id].get('invalide', False)
                                                or find_age(individu_by_id[individu_id], period.start.date,
                                                    default = 0) < 25,
                                            error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                                    u" 25 ans ou être invalide",
                                            ),
                                        ),
                                    ),
                                default = conv.noop,
                                ),
                            ),
                        conv.empty_to_none,
                        conv.not_none,
                        ),
                    # individus = conv.uniform_sequence(
                    #     conv.struct(
                    #         dict(
                    #             date_naissance = conv.test(
                    #                 lambda date_naissance: period.start.date - date_naissance >= datetime.timedelta(0),
                    #                 error = u"L'individu doit être né au plus tard le jour de la simulation",
                    #                 ),
                    #             ),
                    #         default = conv.noop,
                    #         drop_none_values = 'missing',
                    #         ),
                    #     ),
                    menages = conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    personne_de_reference = conv.not_none,
                                    ),
                                default = conv.noop,
                                ),
                            ),
                        conv.empty_to_none,
                        conv.not_none,
                        ),
                    ),
                default = conv.noop,
                )(test_case, state = state)

            return test_case, error

        return json_or_python_to_test_case
def validate_node_json(node, state=None):
    if node is None:
        return None, None
    state = conv.add_ancestor_to_state(state, node)

    validated_node, error = conv.test_isinstance(dict)(node, state=state)
    if error is not None:
        conv.remove_ancestor_from_state(state, node)
        return validated_node, error

    validated_node, errors = conv.struct(
        {
            '@context':
            conv.pipe(
                conv.test_isinstance(basestring),
                conv.make_input_to_url(full=True),
                conv.test_equals(
                    u'http://openfisca.fr/contexts/legislation.jsonld'),
            ),
            '@type':
            conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_line,
                conv.test_in((u'Node', u'Parameter', u'Scale')),
                conv.not_none,
            ),
            'comment':
            conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_text,
            ),
            'description':
            conv.pipe(
                conv.test_isinstance(basestring),
                conv.cleanup_line,
            ),
        },
        constructor=collections.OrderedDict,
        default=conv.noop,
        drop_none_values='missing',
        keep_value_order=True,
    )(validated_node, state=state)
    if errors is not None:
        conv.remove_ancestor_from_state(state, node)
        return validated_node, errors

    validated_node.pop(
        '@context', None
    )  # Remove optional @context everywhere. It will be added to root node later.
    node_converters = {
        '@type': conv.noop,
        'comment': conv.noop,
        'description': conv.noop,
    }
    node_type = validated_node['@type']
    if node_type == u'Node':
        node_converters.update(
            dict(children=conv.pipe(
                conv.test_isinstance(dict),
                conv.uniform_mapping(
                    conv.pipe(
                        conv.test_isinstance(basestring),
                        conv.cleanup_line,
                        conv.not_none,
                    ),
                    conv.pipe(
                        validate_node_json,
                        conv.not_none,
                    ),
                ),
                conv.empty_to_none,
                conv.not_none,
            ), ))
    elif node_type == u'Parameter':
        node_converters.update(
            dict(
                format=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in([
                        'boolean',
                        'float',
                        'integer',
                        'rate',
                    ]),
                ),
                unit=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in(units),
                ),
                values=conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_value_json,
                        drop_none_items=True,
                    ),
                    make_validate_values_json_dates(
                        require_consecutive_dates=True),
                    conv.empty_to_none,
                    conv.not_none,
                ),
            ))
    else:
        assert node_type == u'Scale'
        node_converters.update(
            dict(
                option=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in((
                        'contrib',
                        'main-d-oeuvre',
                        'noncontrib',
                    )),
                ),
                slices=conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_slice_json,
                        drop_none_items=True,
                    ),
                    validate_slices_json_dates,
                    conv.empty_to_none,
                    conv.not_none,
                ),
                unit=conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in(('currency', )),
                ),
            ))
    validated_node, errors = conv.struct(
        node_converters,
        constructor=collections.OrderedDict,
        drop_none_values='missing',
        keep_value_order=True,
    )(validated_node, state=state)

    conv.remove_ancestor_from_state(state, node)
    return validated_node, errors
        def json_or_python_to_test_case(value, state = None):
            if value is None:
                return value, None
            if state is None:
                state = conv.default_state

            column_by_name = self.tax_benefit_system.column_by_name

            # First validation and conversion step
            test_case, error = conv.pipe(
                conv.test_isinstance(dict),
                conv.struct(
                    dict(
                        familles = conv.pipe(
                            conv.condition(
                                conv.test_isinstance(list),
                                conv.pipe(
                                    conv.uniform_sequence(
                                        conv.test_isinstance(dict),
                                        drop_none_items = True,
                                        ),
                                    conv.function(lambda values: collections.OrderedDict(
                                        (value.pop('id', index), value)
                                        for index, value in enumerate(values)
                                        )),
                                    ),
                                ),
                            conv.test_isinstance(dict),
                            conv.uniform_mapping(
                                conv.pipe(
                                    conv.test_isinstance((basestring, int)),
                                    conv.not_none,
                                    ),
                                conv.pipe(
                                    conv.test_isinstance(dict),
                                    conv.struct(
                                        dict(itertools.chain(
                                            dict(
                                                enfants = conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance((basestring, int)),
                                                        drop_none_items = True,
                                                        ),
                                                    conv.default([]),
                                                    ),
                                                parents = conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance((basestring, int)),
                                                        drop_none_items = True,
                                                        ),
                                                    conv.default([]),
                                                    ),
                                                ).iteritems(),
                                            (
                                                (column.name, column.json_to_python)
                                                for column in column_by_name.itervalues()
                                                if column.entity == 'fam'
                                                ),
                                            )),
                                        drop_none_values = True,
                                        ),
                                    ),
                                drop_none_values = True,
                                ),
                            conv.default({}),
                            ),
                        individus = conv.pipe(
                            conv.condition(
                                conv.test_isinstance(list),
                                conv.pipe(
                                    conv.uniform_sequence(
                                        conv.test_isinstance(dict),
                                        drop_none_items = True,
                                        ),
                                    conv.function(lambda values: collections.OrderedDict(
                                        (value.pop('id', index), value)
                                        for index, value in enumerate(values)
                                        )),
                                    ),
                                ),
                            conv.test_isinstance(dict),
                            conv.uniform_mapping(
                                conv.pipe(
                                    conv.test_isinstance((basestring, int)),
                                    conv.not_none,
                                    ),
                                conv.pipe(
                                    conv.test_isinstance(dict),
                                    conv.struct(
                                        dict(
                                            (column.name, column.json_to_python)
                                            for column in column_by_name.itervalues()
                                            if column.entity == 'ind' and column.name not in (
                                                'idfam', 'idfoy', 'idmen', 'quifam', 'quifoy', 'quimen')
                                            ),
                                        drop_none_values = True,
                                        ),
                                    ),
                                drop_none_values = True,
                                ),
                            conv.empty_to_none,
                            conv.not_none,
                            ),
                        ),
                    ),
                )(value, state = state)
            if error is not None:
                return test_case, error

            # Second validation step
            familles_individus_id = list(test_case['individus'].iterkeys())
            test_case, error = conv.struct(
                dict(
                    familles = conv.uniform_mapping(
                        conv.noop,
                        conv.struct(
                            dict(
                                enfants = conv.uniform_sequence(conv.test_in_pop(familles_individus_id)),
                                parents = conv.uniform_sequence(conv.test_in_pop(familles_individus_id)),
                                ),
                            default = conv.noop,
                            ),
                        ),
                    ),
                default = conv.noop,
                )(test_case, state = state)

            remaining_individus_id = set(familles_individus_id)
            if remaining_individus_id:
                if error is None:
                    error = {}
                for individu_id in remaining_individus_id:
                    error.setdefault('individus', {})[individu_id] = state._(u"Individual is missing from {}").format(
                        state._(u' & ').join(
                            word
                            for word in [
                                u'familles' if individu_id in familles_individus_id else None,
                                ]
                            if word is not None
                            ))
            if error is not None:
                return test_case, error

            return test_case, error
        def json_or_python_to_test_case(value, state=None):
            if value is None:
                return value, None
            if state is None:
                state = conv.default_state

            column_by_name = self.tax_benefit_system.column_by_name

            # First validation and conversion step
            test_case, error = conv.pipe(
                conv.test_isinstance(dict),
                conv.struct(
                    dict(
                        individus=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(
                                conv.test_isinstance(dict),
                                drop_none_items=True,
                            ),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(id=conv.pipe(
                                                conv.test_isinstance(
                                                    (basestring, int)),
                                                conv.not_none,
                                            ), ).iteritems(),
                                            ((column.name,
                                              column.json_to_python) for column
                                             in column_by_name.itervalues()
                                             if column.entity == 'ind' and
                                             column.name not in ('idmen',
                                                                 'quimen')),
                                        )),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.empty_to_none,
                            conv.not_none,
                        ),
                        menages=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(
                                conv.test_isinstance(dict),
                                drop_none_items=True,
                            ),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(
                                                autres=conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                                conjoint=conv.pipe(
                                                    conv.test_isinstance(
                                                        basestring, int),
                                                    conv.default(None),
                                                ),
                                                enfants=conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                                id=conv.pipe(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    conv.not_none,
                                                ),
                                                personne_de_reference=conv.
                                                pipe(
                                                    conv.test_isinstance(
                                                        basestring, int),
                                                    conv.default(None),
                                                ),
                                            ).iteritems(),
                                            ((column.name,
                                              column.json_to_python) for column
                                             in column_by_name.itervalues()
                                             if column.entity == 'men'),
                                        )),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.default({}),
                        ),
                    ), ),
            )(value, state=state)
            if error is not None:
                return test_case, error

            # Second validation step
            menages_individus_id = [
                individu['id'] for individu in test_case['individus']
            ]
            test_case, error = conv.struct(
                dict(menages=conv.uniform_sequence(
                    conv.struct(
                        dict(
                            autres=conv.uniform_sequence(
                                conv.test_in_pop(menages_individus_id)),
                            conjoint=conv.test_in_pop(menages_individus_id),
                            enfants=conv.uniform_sequence(
                                conv.test_in_pop(menages_individus_id)),
                            personne_de_reference=conv.test_in_pop(
                                menages_individus_id),
                        ),
                        default=conv.noop,
                    ), ), ),
                default=conv.noop,
            )(test_case, state=state)

            remaining_individus_id = set(menages_individus_id)
            if remaining_individus_id:
                individu_index_by_id = {
                    individu[u'id']: individu_index
                    for individu_index, individu in enumerate(
                        test_case[u'individus'])
                }
                if error is None:
                    error = {}
                for individu_id in remaining_individus_id:
                    error.setdefault(
                        'individus',
                        {})[individu_index_by_id[individu_id]] = state._(
                            u"Individual is missing from {}").format(
                                state._(u' & ').join(word for word in [
                                    u'menages' if individu_id in
                                    menages_individus_id else None,
                                ] if word is not None))
            if error is not None:
                return test_case, error

            return test_case, error
def validate_node_xml_json(node, state = None):
    if node is None:
        return None, None
    state = conv.add_ancestor_to_state(state, node)
    validated_node, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                BAREME = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_scale_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    ),
                CODE = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_parameter_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    ),
                code = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    conv.not_none,
                    ),
                description = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    ),
                NODE = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_node_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    ),
                tail = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                text = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(node, state = state)
    if errors is None:
        children_groups_key = ('BAREME', 'CODE', 'NODE', 'VALBYTRANCHES')
        if all(
                validated_node.get(key) is None
                for key in children_groups_key
                ):
            error = state._(u"At least one of the following items must be present: {}").format(state._(u', ').join(
                u'"{}"'.format(key)
                for key in children_groups_key
                ))
            errors = dict(
                (key, error)
                for key in children_groups_key
                )
        else:
            errors = {}
        children_code = set()
        for key in children_groups_key:
            for child_index, child in enumerate(validated_node.get(key) or []):
                child_code = child['code']
                if child_code in children_code:
                    errors.setdefault(key, {}).setdefault(child_index, {})['code'] = state._(u"Duplicate value")
                else:
                    children_code.add(child_code)
    conv.remove_ancestor_from_state(state, node)
    return validated_node, errors or None
    def post_process_test_case(self, test_case, period, state):

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
        }

        parents_id = set(parent_id for famille in test_case['familles']
                         for parent_id in famille['parents'])
        test_case, error = conv.struct(
            dict(
                familles=conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                enfants=conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                        individu_by_id[individu_id].get(
                                            'handicap', False) or find_age(
                                                individu_by_id[individu_id],
                                                period.start.date,
                                                default=0) <= 25,
                                        error=
                                        u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                        u" 25 ans ou être handicapée",
                                    ), ),
                                parents=conv.pipe(
                                    conv.empty_to_none, conv.not_none,
                                    conv.test(
                                        lambda parents: len(parents) <= 2,
                                        error=N_(
                                            u'A "famille" must have at most 2 "parents"'
                                        ))),
                            ),
                            default=conv.noop,
                        ), ),
                    conv.empty_to_none,
                    conv.not_none,
                ),
                foyers_fiscaux=conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                declarants=conv.pipe(
                                    conv.empty_to_none,
                                    conv.not_none,
                                    conv.test(
                                        lambda declarants: len(declarants) <=
                                        2,
                                        error=N_(
                                            u'A "foyer_fiscal" must have at most 2 "declarants"'
                                        ),
                                    ),
                                    conv.uniform_sequence(conv.pipe()),
                                ),
                                personnes_a_charge=conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                        individu_by_id[individu_id].get(
                                            'handicap', False) or find_age(
                                                individu_by_id[individu_id],
                                                period.start.date,
                                                default=0) <= 25,
                                        error=
                                        u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                        u" 25 ans ou être handicapée",
                                    ), ),
                            ),
                            default=conv.noop,
                        ), ),
                    conv.empty_to_none,
                    conv.not_none,
                ),
                menages=conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(personne_de_reference=conv.not_none, ),
                            default=conv.noop,
                        ), ),
                    conv.empty_to_none,
                    conv.not_none,
                ),
            ),
            default=conv.noop,
        )(test_case, state=state)

        return test_case, error
Example #27
0
    def attribute_groupless_persons_to_entities(self, test_case, period,
                                                groupless_individus):
        individus_without_menage = groupless_individus['menages']
        individus_without_foyer_fiscal = groupless_individus['foyers_fiscaux']

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
        }

        # Affecte à un foyer fiscal chaque individu qui n'appartient à aucun d'entre eux.
        new_foyer_fiscal = dict(
            declarants=[],
            personnes_a_charge=[],
        )
        new_foyer_fiscal_id = None
        for individu_id in individus_without_foyer_fiscal[:]:
            # Tente d'affecter l'individu à un foyer fiscal d'après son ménage.
            menage, menage_role = find_menage_and_role(test_case, individu_id)
            if menage_role == u'personne_de_reference':
                conjoint_id = menage[u'conjoint']
                if conjoint_id is not None:
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                        test_case, conjoint_id)
                    if other_role == u'declarants' and len(
                            foyer_fiscal[u'declarants']) == 1:
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est personne de
                        # référence dans un ménage, qu'il y a un conjoint dans ce ménage et que ce
                        # conjoint est seul déclarant dans un foyer fiscal, alors ajoute l'individu comme
                        # autre déclarant de ce foyer fiscal.
                        foyer_fiscal[u'declarants'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
            elif menage_role == u'conjoint':
                personne_de_reference_id = menage[u'personne_de_reference']
                if personne_de_reference_id is not None:
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                        test_case, personne_de_reference_id)
                    if other_role == u'declarants' and len(
                            foyer_fiscal[u'declarants']) == 1:
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est conjoint
                        # dans un ménage, qu'il y a une personne de référence dans ce ménage et que
                        # cette personne est seul déclarant dans un foyer fiscal, alors ajoute l'individu
                        # comme autre déclarant de ce foyer fiscal.
                        foyer_fiscal[u'declarants'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
            elif menage_role == u'enfants' and (
                    menage['personne_de_reference'] is not None
                    or menage[u'conjoint'] is not None):
                for other_id in (menage['personne_de_reference'],
                                 menage[u'conjoint']):
                    if other_id is None:
                        continue
                    foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                        test_case, other_id)
                    if other_role == u'declarants':
                        # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est enfant dans
                        # un ménage, qu'il y a une personne à charge ou un conjoint dans ce ménage et que
                        # celui-ci est déclarant dans un foyer fiscal, alors ajoute l'individu comme
                        # personne à charge de ce foyer fiscal.
                        foyer_fiscal[u'personnes_a_charge'].append(individu_id)
                        individus_without_foyer_fiscal.remove(individu_id)
                        break

            if individu_id in individus_without_foyer_fiscal:
                # L'individu n'est toujours pas affecté à un foyer fiscal.
                individu = individu_by_id[individu_id]
                age = find_age(individu, period.start.date)
                if len(new_foyer_fiscal[u'declarants']) < 2 and (age is None
                                                                 or age >= 18):
                    new_foyer_fiscal[u'declarants'].append(individu_id)
                else:
                    new_foyer_fiscal[u'personnes_a_charge'].append(individu_id)
                if new_foyer_fiscal_id is None:
                    new_foyer_fiscal[u'id'] = new_foyer_fiscal_id = unicode(
                        uuid.uuid4())
                    test_case[u'foyers_fiscaux'].append(new_foyer_fiscal)
                individus_without_foyer_fiscal.remove(individu_id)

            # Affecte à un ménage chaque individu qui n'appartient à aucun d'entre eux.
            new_menage = dict(
                autres=[],
                conjoint=None,
                enfants=[],
                personne_de_reference=None,
            )
            new_menage_id = None
            for individu_id in menages_individus_id[:]:
                # Tente d'affecter l'individu à un ménage d'après son foyer fiscal.
                foyer_fiscal, foyer_fiscal_role = find_foyer_fiscal_and_role(
                    test_case, individu_id)
                if foyer_fiscal_role == u'declarants' and len(
                        foyer_fiscal[u'declarants']) == 2:
                    for declarant_id in foyer_fiscal[u'declarants']:
                        if declarant_id != individu_id:
                            menage, other_role = find_menage_and_role(
                                test_case, declarant_id)
                            if other_role == u'personne_de_reference' and menage[
                                    u'conjoint'] is None:
                                # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                # dans un foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et que
                                # cet autre déclarant est personne de référence dans un ménage et qu'il n'y a
                                # pas de conjoint dans ce ménage, alors ajoute l'individu comme conjoint de ce
                                # ménage.
                                menage[u'conjoint'] = individu_id
                                menages_individus_id.remove(individu_id)
                            elif other_role == u'conjoint' and menage[
                                    u'personne_de_reference'] is None:
                                # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                # dans une foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et
                                # que cet autre déclarant est conjoint dans un ménage et qu'il n'y a pas de
                                # personne de référence dans ce ménage, alors ajoute l'individu comme personne
                                # de référence de ce ménage.
                                menage[u'personne_de_reference'] = individu_id
                                menages_individus_id.remove(individu_id)
                            break
                elif foyer_fiscal_role == u'personnes_a_charge' and foyer_fiscal[
                        u'declarants']:
                    for declarant_id in foyer_fiscal[u'declarants']:
                        menage, other_role = find_menage_and_role(
                            test_case, declarant_id)
                        if other_role in (u'personne_de_reference',
                                          u'conjoint'):
                            # Quand l'individu n'est pas encore dans un ménage, mais qu'il est personne à charge
                            # dans un foyer fiscal, qu'il y a un déclarant dans ce foyer fiscal et que ce
                            # déclarant est personne de référence ou conjoint dans un ménage, alors ajoute
                            # l'individu comme enfant de ce ménage.
                            menage[u'enfants'].append(individu_id)
                            menages_individus_id.remove(individu_id)
                            break

                if individu_id in menages_individus_id:
                    # L'individu n'est toujours pas affecté à un ménage.
                    if new_menage[u'personne_de_reference'] is None:
                        new_menage[u'personne_de_reference'] = individu_id
                    elif new_menage[u'conjoint'] is None:
                        new_menage[u'conjoint'] = individu_id
                    else:
                        new_menage[u'enfants'].append(individu_id)
                    if new_menage_id is None:
                        new_menage[u'id'] = new_menage_id = unicode(
                            uuid.uuid4())
                        test_case[u'menages'].append(new_menage)
                    menages_individus_id.remove(individu_id)

        remaining_individus_id = set(individus_without_foyer_fiscal).union(
            menages_individus_id)
        if remaining_individus_id:
            individu_index_by_id = {
                individu[u'id']: individu_index
                for individu_index, individu in enumerate(
                    test_case[u'individus'])
            }
            if error is None:
                error = {}
            for individu_id in remaining_individus_id:
                error.setdefault(
                    'individus',
                    {})[individu_index_by_id[individu_id]] = state._(
                        u"Individual is missing from {}").format(
                            state._(u' & ').join(word for word in [
                                u'foyers_fiscaux' if individu_id in
                                individus_without_foyer_fiscal else None,
                                u'menages' if individu_id in
                                menages_individus_id else None,
                            ] if word is not None))
        if error is not None:
            return test_case, error

            # Third validation step
            individu_by_id = test_case['individus']
            test_case, error = conv.struct(
                dict(
                    foyers_fiscaux=conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    declarants=conv.pipe(
                                        conv.empty_to_none,
                                        conv.not_none,
                                        conv.test(
                                            lambda declarants: len(declarants)
                                            <= 2,
                                            error=N_(
                                                u'A "foyer_fiscal" must have at most 2 "declarants"'
                                            ),
                                        ),
                                        # conv.uniform_sequence(conv.pipe(
                                        # conv.test(lambda individu_id:
                                        #     find_age(individu_by_id[individu_id], period.start.date,
                                        #         default = 100) >= 18,
                                        #     error = u"Un déclarant d'un foyer fiscal doit être agé d'au moins 18"
                                        #         u" ans",
                                        #     ),
                                        # conv.test(
                                        # lambda individu_id: individu_id in parents_id,
                                        # error = u"Un déclarant ou un conjoint sur la déclaration d'impôt, doit"
                                        # u" être un parent dans sa famille",
                                        # ),
                                        # )),
                                    ),
                                    personnes_a_charge=conv.uniform_sequence(
                                        conv.test(
                                            lambda individu_id:
                                            individu_by_id[individu_id].get(
                                                'invalide', False) or find_age(
                                                    individu_by_id[individu_id
                                                                   ],
                                                    period.start.date,
                                                    default=0) < 25,
                                            error=
                                            u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                            u" 25 ans ou être invalide",
                                        ), ),
                                ),
                                default=conv.noop,
                            ), ),
                        conv.empty_to_none,
                        conv.not_none,
                    ),
                    # individus = conv.uniform_sequence(
                    #     conv.struct(
                    #         dict(
                    #             date_naissance = conv.test(
                    #                 lambda date_naissance: period.start.date - date_naissance >= datetime.timedelta(0),
                    #                 error = u"L'individu doit être né au plus tard le jour de la simulation",
                    #                 ),
                    #             ),
                    #         default = conv.noop,
                    #         drop_none_values = 'missing',
                    #         ),
                    #     ),
                    menages=conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(personne_de_reference=conv.not_none, ),
                                default=conv.noop,
                            ), ),
                        conv.empty_to_none,
                        conv.not_none,
                    ),
                ),
                default=conv.noop,
            )(test_case, state=state)

            return test_case, error

        return json_or_python_to_test_case
Example #28
0
        def json_or_python_to_test_case(value, state=None):
            if value is None:
                return value, None
            if state is None:
                state = conv.default_state

            column_by_name = self.tax_benefit_system.column_by_name

            # First validation and conversion step
            test_case, error = conv.pipe(
                conv.test_isinstance(dict),
                conv.struct(
                    dict(
                        foyers_fiscaux=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(
                                conv.test_isinstance(dict),
                                drop_none_items=True,
                            ),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(
                                                declarants=conv.pipe(
                                                    conv.
                                                    make_item_to_singleton(),
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                                id=conv.pipe(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    conv.not_none,
                                                ),
                                                personnes_a_charge=conv.pipe(
                                                    conv.
                                                    make_item_to_singleton(),
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                            ).iteritems(),
                                            ((column.name,
                                              column.json_to_python) for column
                                             in column_by_name.itervalues()
                                             if column.entity == 'fam'),
                                        )),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.default([]),
                        ),
                        individus=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(
                                conv.test_isinstance(dict),
                                drop_none_items=True,
                            ),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(id=conv.pipe(
                                                conv.test_isinstance(
                                                    (basestring, int)),
                                                conv.not_none,
                                            ), ).iteritems(),
                                            ((column.name,
                                              column.json_to_python) for column
                                             in column_by_name.itervalues()
                                             if column.entity == 'ind'
                                             and column.name not in
                                             ('idfam', 'idfoy', 'idmen',
                                              'quifam', 'quifoy', 'quimen')),
                                        )),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.empty_to_none,
                            conv.not_none,
                        ),
                        menages=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(
                                conv.test_isinstance(dict),
                                drop_none_items=True,
                            ),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(
                                                autres=conv.pipe(
                                                    # personnes ayant un lien autre avec la personne de référence
                                                    conv.
                                                    make_item_to_singleton(),
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                                # conjoint de la personne de référence
                                                conjoint=conv.test_isinstance(
                                                    (basestring, int)),
                                                enfants=conv.pipe(
                                                    # enfants de la personne de référence ou de son conjoint
                                                    conv.
                                                    make_item_to_singleton(),
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance(
                                                            (basestring, int)),
                                                        drop_none_items=True,
                                                    ),
                                                    conv.default([]),
                                                ),
                                                id=conv.pipe(
                                                    conv.test_isinstance(
                                                        (basestring, int)),
                                                    conv.not_none,
                                                ),
                                                personne_de_reference=conv.
                                                test_isinstance(
                                                    (basestring, int)),
                                            ).iteritems(),
                                            ((column.name,
                                              column.json_to_python) for column
                                             in column_by_name.itervalues()
                                             if column.entity == 'men'),
                                        )),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.default([]),
                        ),
                    ), ),
            )(value, state=state)
            if error is not None:
                return test_case, error

            # Second validation step
            foyers_fiscaux_individus_id = [
                individu['id'] for individu in test_case['individus']
            ]
            menages_individus_id = [
                individu['id'] for individu in test_case['individus']
            ]
            test_case, error = conv.struct(
                dict(
                    foyers_fiscaux=conv.uniform_sequence(
                        conv.struct(
                            dict(
                                declarants=conv.uniform_sequence(
                                    conv.test_in_pop(
                                        foyers_fiscaux_individus_id)),
                                personnes_a_charge=conv.uniform_sequence(
                                    conv.test_in_pop(
                                        foyers_fiscaux_individus_id)),
                            ),
                            default=conv.noop,
                        ), ),
                    menages=conv.uniform_sequence(
                        conv.struct(
                            dict(
                                autres=conv.uniform_sequence(
                                    conv.test_in_pop(menages_individus_id)),
                                conjoint=conv.test_in_pop(
                                    menages_individus_id),
                                enfants=conv.uniform_sequence(
                                    conv.test_in_pop(menages_individus_id)),
                                personne_de_reference=conv.test_in_pop(
                                    menages_individus_id),
                            ),
                            default=conv.noop,
                        ), ),
                ),
                default=conv.noop,
            )(test_case, state=state)

            individu_by_id = {
                individu['id']: individu
                for individu in test_case['individus']
            }

            if repair:
                # Affecte à un foyer fiscal chaque individu qui n'appartient à aucun d'entre eux.
                new_foyer_fiscal = dict(
                    declarants=[],
                    personnes_a_charge=[],
                )
                new_foyer_fiscal_id = None
                for individu_id in foyers_fiscaux_individus_id[:]:

                    # Tente d'affecter l'individu à un foyer fiscal d'après son ménage.
                    menage, menage_role = find_menage_and_role(
                        test_case, individu_id)
                    if menage_role == u'personne_de_reference':
                        conjoint_id = menage[u'conjoint']
                        if conjoint_id is not None:
                            foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                                test_case, conjoint_id)
                            if other_role == u'declarants' and len(
                                    foyer_fiscal[u'declarants']) == 1:
                                # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est personne de
                                # référence dans un ménage, qu'il y a un conjoint dans ce ménage et que ce
                                # conjoint est seul déclarant dans un foyer fiscal, alors ajoute l'individu comme
                                # autre déclarant de ce foyer fiscal.
                                foyer_fiscal[u'declarants'].append(individu_id)
                                foyers_fiscaux_individus_id.remove(individu_id)
                    elif menage_role == u'conjoint':
                        personne_de_reference_id = menage[
                            u'personne_de_reference']
                        if personne_de_reference_id is not None:
                            foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                                test_case, personne_de_reference_id)
                            if other_role == u'declarants' and len(
                                    foyer_fiscal[u'declarants']) == 1:
                                # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est conjoint
                                # dans un ménage, qu'il y a une personne de référence dans ce ménage et que
                                # cette personne est seul déclarant dans un foyer fiscal, alors ajoute l'individu
                                # comme autre déclarant de ce foyer fiscal.
                                foyer_fiscal[u'declarants'].append(individu_id)
                                foyers_fiscaux_individus_id.remove(individu_id)
                    elif menage_role == u'enfants' and (
                            menage['personne_de_reference'] is not None
                            or menage[u'conjoint'] is not None):
                        for other_id in (menage['personne_de_reference'],
                                         menage[u'conjoint']):
                            if other_id is None:
                                continue
                            foyer_fiscal, other_role = find_foyer_fiscal_and_role(
                                test_case, other_id)
                            if other_role == u'declarants':
                                # Quand l'individu n'est pas encore dans un foyer fiscal, mais qu'il est enfant dans
                                # un ménage, qu'il y a une personne à charge ou un conjoint dans ce ménage et que
                                # celui-ci est déclarant dans un foyer fiscal, alors ajoute l'individu comme
                                # personne à charge de ce foyer fiscal.
                                foyer_fiscal[u'personnes_a_charge'].append(
                                    individu_id)
                                foyers_fiscaux_individus_id.remove(individu_id)
                                break

                    if individu_id in foyers_fiscaux_individus_id:
                        # L'individu n'est toujours pas affecté à un foyer fiscal.
                        individu = individu_by_id[individu_id]
                        age = find_age(individu, period.start.date)
                        if len(new_foyer_fiscal[u'declarants']) < 2 and (
                                age is None or age >= 18):
                            new_foyer_fiscal[u'declarants'].append(individu_id)
                        else:
                            new_foyer_fiscal[u'personnes_a_charge'].append(
                                individu_id)
                        if new_foyer_fiscal_id is None:
                            new_foyer_fiscal[
                                u'id'] = new_foyer_fiscal_id = unicode(
                                    uuid.uuid4())
                            test_case[u'foyers_fiscaux'].append(
                                new_foyer_fiscal)
                        foyers_fiscaux_individus_id.remove(individu_id)

                # Affecte à un ménage chaque individu qui n'appartient à aucun d'entre eux.
                new_menage = dict(
                    autres=[],
                    conjoint=None,
                    enfants=[],
                    personne_de_reference=None,
                )
                new_menage_id = None
                for individu_id in menages_individus_id[:]:
                    # Tente d'affecter l'individu à un ménage d'après son foyer fiscal.
                    foyer_fiscal, foyer_fiscal_role = find_foyer_fiscal_and_role(
                        test_case, individu_id)
                    if foyer_fiscal_role == u'declarants' and len(
                            foyer_fiscal[u'declarants']) == 2:
                        for declarant_id in foyer_fiscal[u'declarants']:
                            if declarant_id != individu_id:
                                menage, other_role = find_menage_and_role(
                                    test_case, declarant_id)
                                if other_role == u'personne_de_reference' and menage[
                                        u'conjoint'] is None:
                                    # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                    # dans un foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et que
                                    # cet autre déclarant est personne de référence dans un ménage et qu'il n'y a
                                    # pas de conjoint dans ce ménage, alors ajoute l'individu comme conjoint de ce
                                    # ménage.
                                    menage[u'conjoint'] = individu_id
                                    menages_individus_id.remove(individu_id)
                                elif other_role == u'conjoint' and menage[
                                        u'personne_de_reference'] is None:
                                    # Quand l'individu n'est pas encore dans un ménage, mais qu'il est déclarant
                                    # dans une foyer fiscal, qu'il y a un autre déclarant dans ce foyer fiscal et
                                    # que cet autre déclarant est conjoint dans un ménage et qu'il n'y a pas de
                                    # personne de référence dans ce ménage, alors ajoute l'individu comme personne
                                    # de référence de ce ménage.
                                    menage[
                                        u'personne_de_reference'] = individu_id
                                    menages_individus_id.remove(individu_id)
                                break
                    elif foyer_fiscal_role == u'personnes_a_charge' and foyer_fiscal[
                            u'declarants']:
                        for declarant_id in foyer_fiscal[u'declarants']:
                            menage, other_role = find_menage_and_role(
                                test_case, declarant_id)
                            if other_role in (u'personne_de_reference',
                                              u'conjoint'):
                                # Quand l'individu n'est pas encore dans un ménage, mais qu'il est personne à charge
                                # dans un foyer fiscal, qu'il y a un déclarant dans ce foyer fiscal et que ce
                                # déclarant est personne de référence ou conjoint dans un ménage, alors ajoute
                                # l'individu comme enfant de ce ménage.
                                menage[u'enfants'].append(individu_id)
                                menages_individus_id.remove(individu_id)
                                break

                    if individu_id in menages_individus_id:
                        # L'individu n'est toujours pas affecté à un ménage.
                        if new_menage[u'personne_de_reference'] is None:
                            new_menage[u'personne_de_reference'] = individu_id
                        elif new_menage[u'conjoint'] is None:
                            new_menage[u'conjoint'] = individu_id
                        else:
                            new_menage[u'enfants'].append(individu_id)
                        if new_menage_id is None:
                            new_menage[u'id'] = new_menage_id = unicode(
                                uuid.uuid4())
                            test_case[u'menages'].append(new_menage)
                        menages_individus_id.remove(individu_id)

            remaining_individus_id = set(foyers_fiscaux_individus_id).union(
                menages_individus_id)
            if remaining_individus_id:
                individu_index_by_id = {
                    individu[u'id']: individu_index
                    for individu_index, individu in enumerate(
                        test_case[u'individus'])
                }
                if error is None:
                    error = {}
                for individu_id in remaining_individus_id:
                    error.setdefault(
                        'individus',
                        {})[individu_index_by_id[individu_id]] = state._(
                            u"Individual is missing from {}").format(
                                state._(u' & ').join(word for word in [
                                    u'foyers_fiscaux' if individu_id in
                                    foyers_fiscaux_individus_id else None,
                                    u'menages' if individu_id in
                                    menages_individus_id else None,
                                ] if word is not None))
            if error is not None:
                return test_case, error

            # Third validation step
            individu_by_id = test_case['individus']
            test_case, error = conv.struct(
                dict(
                    foyers_fiscaux=conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(
                                    declarants=conv.pipe(
                                        conv.empty_to_none,
                                        conv.not_none,
                                        conv.test(
                                            lambda declarants: len(declarants)
                                            <= 2,
                                            error=N_(
                                                u'A "foyer_fiscal" must have at most 2 "declarants"'
                                            ),
                                        ),
                                        # conv.uniform_sequence(conv.pipe(
                                        # conv.test(lambda individu_id:
                                        #     find_age(individu_by_id[individu_id], period.start.date,
                                        #         default = 100) >= 18,
                                        #     error = u"Un déclarant d'un foyer fiscal doit être agé d'au moins 18"
                                        #         u" ans",
                                        #     ),
                                        # conv.test(
                                        # lambda individu_id: individu_id in parents_id,
                                        # error = u"Un déclarant ou un conjoint sur la déclaration d'impôt, doit"
                                        # u" être un parent dans sa famille",
                                        # ),
                                        # )),
                                    ),
                                    personnes_a_charge=conv.uniform_sequence(
                                        conv.test(
                                            lambda individu_id: individu_by_id[
                                                individu_id].get('inv', False)
                                            or find_age(individu_by_id[
                                                individu_id],
                                                        period.start.date,
                                                        default=0) < 25,
                                            error=
                                            u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                            u" 25 ans ou être invalide",
                                        ), ),
                                ),
                                default=conv.noop,
                            ), ),
                        conv.empty_to_none,
                        conv.not_none,
                    ),
                    # individus = conv.uniform_sequence(
                    #     conv.struct(
                    #         dict(
                    #             date_naissance = conv.test(
                    #                 lambda date_naissance: period.start.date - date_naissance >= datetime.timedelta(0),
                    #                 error = u"L'individu doit être né au plus tard le jour de la simulation",
                    #                 ),
                    #             ),
                    #         default = conv.noop,
                    #         drop_none_values = 'missing',
                    #         ),
                    #     ),
                    menages=conv.pipe(
                        conv.uniform_sequence(
                            conv.struct(
                                dict(personne_de_reference=conv.not_none, ),
                                default=conv.noop,
                            ), ),
                        conv.empty_to_none,
                        conv.not_none,
                    ),
                ),
                default=conv.noop,
            )(test_case, state=state)

            return test_case, error
def validate_value_json(value, state=None):
    if value is None:
        return None, None
    container = state.ancestors[-1]
    value_converter = dict(
        boolean=conv.condition(
            conv.test_isinstance(int),
            conv.test_in((0, 1)),
            conv.test_isinstance(bool),
        ),
        float=conv.condition(
            conv.test_isinstance(int),
            conv.anything_to_float,
            conv.test_isinstance(float),
        ),
        integer=conv.condition(
            conv.test_isinstance(float),
            conv.pipe(
                conv.test(lambda number: round(number) == number),
                conv.function(int),
            ),
            conv.test_isinstance(int),
        ),
        rate=conv.condition(
            conv.test_isinstance(int),
            conv.anything_to_float,
            conv.test_isinstance(float),
        ),
    )[container.get('format') or 'float']  # Only parameters have a "format".
    state = conv.add_ancestor_to_state(state, value)
    validated_value, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            {
                u'comment':
                conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                ),
                u'from':
                conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                ),
                u'to':
                conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                ),
                u'value':
                conv.pipe(
                    value_converter,
                    conv.not_none,
                ),
            },
            constructor=collections.OrderedDict,
            drop_none_values='missing',
            keep_value_order=True,
        ),
    )(value, state=state)
    conv.remove_ancestor_from_state(state, value)
    return validated_value, errors
    def post_process_test_case(self, test_case, period, state):

        individu_by_id = {
            individu['id']: individu
            for individu in test_case['individus']
            }

        parents_id = set(  # noqa F841
            parent_id
            for famille in test_case['familles']
            for parent_id in famille['parents']
            )
        test_case, error = conv.struct(
            dict(
                familles = conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                enfants = conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                            individu_by_id[individu_id].get('handicap', False)
                                            or find_age(individu_by_id[individu_id], period.start.date,
                                                default = 0) <= 25,
                                        error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                                u" 25 ans ou être handicapée",
                                        ),
                                    ),
                                parents = conv.pipe(
                                    conv.empty_to_none,
                                    conv.not_none,
                                    conv.test(lambda parents: len(parents) <= 2,
                                        error = N_(u'A "famille" must have at most 2 "parents"'))
                                    ),
                                ),
                            default = conv.noop,
                            ),
                        ),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                foyers_fiscaux = conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                declarants = conv.pipe(
                                    conv.empty_to_none,
                                    conv.not_none,
                                    conv.test(
                                        lambda declarants: len(declarants) <= 2,
                                        error = N_(u'A "foyer_fiscal" must have at most 2 "declarants"'),
                                        ),
                                    conv.uniform_sequence(conv.pipe(
                                        )),
                                    ),
                                personnes_a_charge = conv.uniform_sequence(
                                    conv.test(
                                        lambda individu_id:
                                            individu_by_id[individu_id].get('handicap', False)
                                            or find_age(individu_by_id[individu_id], period.start.date,
                                                default = 0) <= 25,
                                        error = u"Une personne à charge d'un foyer fiscal doit avoir moins de"
                                                u" 25 ans ou être handicapée",
                                        ),
                                    ),
                                ),
                            default = conv.noop,
                            ),
                        ),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                menages = conv.pipe(
                    conv.uniform_sequence(
                        conv.struct(
                            dict(
                                personne_de_reference = conv.not_none,
                                ),
                            default = conv.noop,
                            ),
                        ),
                    conv.empty_to_none,
                    conv.not_none,
                    ),
                ),
            default = conv.noop,
            )(test_case, state = state)

        return test_case, error
def validate_slice_xml_json(slice, state = None):
    if slice is None:
        return None, None
    state = conv.add_ancestor_to_state(state, slice)
    validated_slice, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                ASSIETTE = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_values_holder_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    conv.test(lambda l: len(l) == 1, error = N_(u"List must contain one and only one item")),
                    ),
                code = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    ),
                MONTANT = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_values_holder_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    conv.test(lambda l: len(l) == 1, error = N_(u"List must contain one and only one item")),
                    ),
                SEUIL = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_values_holder_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    conv.test(lambda l: len(l) == 1, error = N_(u"List must contain one and only one item")),
                    conv.not_none,
                    ),
                tail = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                TAUX = conv.pipe(
                    conv.test_isinstance(list),
                    conv.uniform_sequence(
                        validate_values_holder_xml_json,
                        drop_none_items = True,
                        ),
                    conv.empty_to_none,
                    conv.test(lambda l: len(l) == 1, error = N_(u"List must contain one and only one item")),
                    ),
                text = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        conv.test(lambda slice: bool(slice.get('MONTANT')) ^ bool(slice.get('TAUX')),
            error = N_(u"Either MONTANT or TAUX must be provided")),
        )(slice, state = state)
    conv.remove_ancestor_from_state(state, slice)
    return validated_slice, errors
        def json_or_python_to_attributes(value, state=None):
            if value is None:
                return value, None
            if state is None:
                state = conv.default_state

            # First validation and conversion step
            data, error = conv.pipe(
                conv.test_isinstance(dict),
                # TODO: Remove condition below, once every calls uses "period" instead of "date" & "year".
                self.cleanup_period_in_json_or_python,
                conv.struct(
                    dict(
                        axes=make_json_or_python_to_axes(
                            self.tax_benefit_system),
                        input_variables=conv.test_isinstance(
                            dict
                        ),  # Real test is done below, once period is known.
                        period=conv.pipe(
                            conv.function(periods.period),
                            conv.not_none,
                        ),
                        test_case=conv.test_isinstance(
                            dict
                        ),  # Real test is done below, once period is known.
                    ), ),
            )(value, state=state)
            if error is not None:
                return data, error

            # Second validation and conversion step
            data, error = conv.struct(
                dict(
                    input_variables=make_json_or_python_to_input_variables(
                        self.tax_benefit_system, data['period']),
                    test_case=self.make_json_or_python_to_test_case(
                        period=data['period'], repair=repair),
                ),
                default=conv.noop,
            )(data, state=state)
            if error is not None:
                return data, error

            # Third validation and conversion step
            errors = {}
            if data['input_variables'] is not None and data[
                    'test_case'] is not None:
                errors['input_variables'] = state._(
                    "Items input_variables and test_case can't both exist")
                errors['test_case'] = state._(
                    "Items input_variables and test_case can't both exist")
            elif data['axes'] is not None and data["test_case"] is None:
                errors['axes'] = state._(
                    "Axes can't be used with input_variables.")
            if errors:
                return data, errors

            if data['axes'] is not None:
                for parallel_axes_index, parallel_axes in enumerate(
                        data['axes']):
                    first_axis = parallel_axes[0]
                    axis_count = first_axis['count']
                    axis_entity_key = tbs.get_variable(
                        first_axis['name']).entity.key
                    first_axis_period = first_axis['period'] or data['period']
                    for axis_index, axis in enumerate(parallel_axes):
                        if axis['min'] >= axis['max']:
                            errors.setdefault('axes', {}).setdefault(
                                parallel_axes_index, {}
                            ).setdefault(axis_index, {})['max'] = state._(
                                "Max value must be greater than min value")
                        column = tbs.get_variable(axis['name'])
                        if axis['index'] >= len(
                                data['test_case'][column.entity.plural]):
                            errors.setdefault('axes', {}).setdefault(
                                parallel_axes_index, {}).setdefault(
                                    axis_index, {})['index'] = state._(
                                        "Index must be lower than {}").format(
                                            len(data['test_case'][
                                                column.entity.plural]))
                        if axis_index > 0:
                            if axis['count'] != axis_count:
                                errors.setdefault('axes', {}).setdefault(
                                    parallel_axes_index, {}
                                ).setdefault(
                                    axis_index, {}
                                )['count'] = state._(
                                    "Parallel indexes must have the same count"
                                )
                            if column.entity.key != axis_entity_key:
                                errors.setdefault('axes', {}).setdefault(
                                    parallel_axes_index, {}
                                ).setdefault(
                                    axis_index, {}
                                )['period'] = state._(
                                    "Parallel indexes must belong to the same entity"
                                )
                            axis_period = axis['period'] or data['period']
                            if axis_period.unit != first_axis_period.unit:
                                errors.setdefault('axes', {}).setdefault(
                                    parallel_axes_index, {}
                                ).setdefault(
                                    axis_index, {}
                                )['period'] = state._(
                                    "Parallel indexes must have the same period unit"
                                )
                            elif axis_period.size != first_axis_period.size:
                                errors.setdefault('axes', {}).setdefault(
                                    parallel_axes_index, {}
                                ).setdefault(
                                    axis_index, {}
                                )['period'] = state._(
                                    "Parallel indexes must have the same period size"
                                )
                if errors:
                    return data, errors

            self.axes = data['axes']
            if data['input_variables'] is not None:
                self.input_variables = data['input_variables']
            self.period = data['period']
            if data['test_case'] is not None:
                self.test_case = data['test_case']
            return self, None
def validate_value_xml_json(value, state = None):
    if value is None:
        return None, None
    container = state.ancestors[-1]
    value_converter = dict(
        bool = conv.pipe(
            conv.test_isinstance(basestring),
            conv.cleanup_line,
            conv.test_in([u'0', u'1']),
            ),
        float = conv.pipe(
            conv.test_isinstance(basestring),
            conv.cleanup_line,
            conv.test_conv(conv.anything_to_float),
            ),
        integer = conv.pipe(
            conv.test_isinstance(basestring),
            conv.cleanup_line,
            conv.test_conv(conv.anything_to_strict_int),
            ),
        percent = conv.pipe(
            conv.test_isinstance(basestring),
            conv.cleanup_line,
            conv.test_conv(conv.anything_to_float),
            ),
        date = conv.pipe(
            conv.test_isinstance(basestring),
            conv.cleanup_line,
            #TODO: add a new conv ?
            conv.test_conv(conv.anything_to_strict_int),
            ),
        )[container.get('format') or 'float']  # Only CODE have a "format".
    state = conv.add_ancestor_to_state(state, value)
    validated_value, errors = conv.pipe(
        conv.test_isinstance(dict),
        conv.struct(
            dict(
                code = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_line,
                    ),
                deb = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                    ),
                fin = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.iso8601_input_to_date,
                    conv.date_to_iso8601_str,
                    conv.not_none,
                    ),
                format = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in(xml_json_formats),
                    conv.test_equals(container.get('format')),
                    ),
                tail = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                text = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.cleanup_text,
                    ),
                type = conv.pipe(
                    conv.test_isinstance(basestring),
                    conv.input_to_slug,
                    conv.test_in([
                        'age',
                        'days',
                        'hours',
                        'monetary',
                        'months',
                        ]),
                    conv.test_equals(container.get('type')),
                    ),
                valeur = conv.pipe(
                    value_converter,
                    conv.not_none,
                    ),
                ),
            constructor = collections.OrderedDict,
            drop_none_values = 'missing',
            keep_value_order = True,
            ),
        )(value, state = state)
    conv.remove_ancestor_from_state(state, value)
    return validated_value, errors
def make_json_or_python_to_test(tax_benefit_system):
    validate = conv.struct(
        dict(
            itertools.chain(
                dict(
                    absolute_error_margin=conv.pipe(
                        conv.test_isinstance((float, int)),
                        conv.test_greater_or_equal(0),
                    ),
                    axes=make_json_or_python_to_axes(tax_benefit_system),
                    description=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_line,
                    ),
                    input_variables=conv.pipe(
                        conv.test_isinstance(dict),
                        conv.uniform_mapping(
                            conv.pipe(
                                conv.test_isinstance(basestring_type),
                                conv.not_none,
                            ),
                            conv.noop,
                        ),
                        conv.empty_to_none,
                    ),
                    keywords=conv.pipe(
                        conv.make_item_to_singleton(),
                        conv.test_isinstance(list),
                        conv.uniform_sequence(
                            conv.pipe(
                                conv.test_isinstance(basestring_type),
                                conv.cleanup_line,
                            ),
                            drop_none_items=True,
                        ),
                        conv.empty_to_none,
                    ),
                    name=conv.pipe(
                        conv.test_isinstance(basestring_type),
                        conv.cleanup_line,
                    ),
                    output_variables=conv.test_isinstance(dict),
                    period=conv.pipe(
                        conv.function(periods.period),
                        conv.not_none,
                    ),
                    relative_error_margin=conv.pipe(
                        conv.test_isinstance((float, int)),
                        conv.test_greater_or_equal(0),
                    ),
                ).items(),
                ((entity.plural,
                  conv.pipe(
                      conv.make_item_to_singleton(),
                      conv.test_isinstance(list),
                  )) for entity in tax_benefit_system.entities),
            )), )

    def json_or_python_to_test(value, state=None):
        if value is None:
            return value, None
        if state is None:
            state = conv.default_state
        value, error = conv.pipe(
            conv.test_isinstance(dict),
            validate,
        )(value, state=state)
        if error is not None:
            return value, error

        value, error = conv.struct(
            dict(output_variables=make_json_or_python_to_input_variables(
                tax_benefit_system, value['period']), ),
            default=conv.noop,
        )(value, state=state)
        if error is not None:
            return value, error

        test_case = value.copy()
        absolute_error_margin = test_case.pop('absolute_error_margin')
        axes = test_case.pop('axes')
        description = test_case.pop('description')
        input_variables = test_case.pop('input_variables')
        keywords = test_case.pop('keywords')
        name = test_case.pop('name')
        output_variables = test_case.pop('output_variables')
        period = test_case.pop('period')
        relative_error_margin = test_case.pop('relative_error_margin')

        if test_case is not None and all(
                entity_members is None
                for entity_members in test_case.values()):
            test_case = None

        scenario, error = tax_benefit_system.Scenario.make_json_to_instance(
            repair=True, tax_benefit_system=tax_benefit_system)(dict(
                axes=axes,
                input_variables=input_variables,
                period=period,
                test_case=test_case,
            ),
                                                                state=state)
        if error is not None:
            return scenario, error

        return {
            key: value
            for key, value in dict(
                absolute_error_margin=absolute_error_margin,
                description=description,
                keywords=keywords,
                name=name,
                output_variables=output_variables,
                relative_error_margin=relative_error_margin,
                scenario=scenario,
            ).items() if value is not None
        }, None

    return json_or_python_to_test
class TaxBenefitSystem(object):
    """
    Represents the legislation.

    It stores parameters (values defined for everyone) and variables (values defined for some given entity e.g. a person).

    :param entities: Entities used by the tax benefit system.
    :param string parameters: Directory containing the YAML parameter files.


    .. py:attribute:: parameters

       :any:`ParameterNode` containing the legislation parameters
    """
    _base_tax_benefit_system = None
    _parameters_at_instant_cache = None
    person_key_plural = None
    preprocess_parameters = None
    json_to_attributes = staticmethod(
        conv.pipe(
            conv.test_isinstance(dict),
            conv.struct({}),
        ))
    baseline = None  # Baseline tax-benefit system. Used only by reforms. Note: Reforms can be chained.
    Scenario = AbstractScenario
    cache_blacklist = None
    decomposition_file_path = None

    def __init__(self, entities):
        # TODO: Currently: Don't use a weakref, because they are cleared by Paste (at least) at each call.
        self.parameters = None
        self._parameters_at_instant_cache = {}  # weakref.WeakValueDictionary()
        self.variables = {}
        self.open_api_config = {}
        self.entities = entities
        if entities is None or len(entities) == 0:
            raise Exception(
                "A tax and benefit sytem must have at least an entity.")
        self.person_entity = [
            entity for entity in entities if entity.is_person
        ][0]
        self.group_entities = [
            entity for entity in entities if not entity.is_person
        ]

    @property
    def base_tax_benefit_system(self):
        base_tax_benefit_system = self._base_tax_benefit_system
        if base_tax_benefit_system is None:
            baseline = self.baseline
            if baseline is None:
                return self
            self._base_tax_benefit_system = base_tax_benefit_system = baseline.base_tax_benefit_system
        return base_tax_benefit_system

    @classmethod
    def json_to_instance(cls, value, state=None):
        attributes, error = conv.pipe(
            cls.json_to_attributes,
            conv.default({}),
        )(value, state=state or conv.default_state)
        if error is not None:
            return attributes, error
        return cls(**attributes), None

    def new_scenario(self):
        scenario = self.Scenario()
        scenario.tax_benefit_system = self
        return scenario

    def prefill_cache(self):
        pass

    def load_variable(self, variable_class, update=False):
        name = to_unicode(variable_class.__name__)

        # Check if a Variable with the same name is already registered.
        baseline_variable = self.get_variable(name)
        if baseline_variable and not update:
            raise VariableNameConflict(
                'Variable "{}" is already defined. Use `update_variable` to replace it.'
                .format(name))

        variable = variable_class(baseline_variable=baseline_variable)
        self.variables[variable.name] = variable

        return variable

    def add_variable(self, variable):
        """
        Adds an OpenFisca variable to the tax and benefit system.

        :param Variable variable: The variable to add. Must be a subclass of Variable.

        :raises: :any:`VariableNameConflict` if a variable with the same name have previously been added to the tax and benefit system.
        """
        return self.load_variable(variable, update=False)

    def replace_variable(self, variable):
        """
        Replaces an existing OpenFisca variable in the tax and benefit system by a new one.

        The new variable must have the same name than the replaced one.

        If no variable with the given name exists in the tax and benefit system, no error will be raised and the new variable will be simply added.

        :param Variable variable: New variable to add. Must be a subclass of Variable.
        """
        name = to_unicode(variable.__name__)
        if self.variables.get(name) is not None:
            del self.variables[name]
        self.load_variable(variable, update=False)

    def update_variable(self, variable):
        """
        Updates an existing OpenFisca variable in the tax and benefit system.

        All attributes of the updated variable that are not explicitely overridden by the new ``variable`` will stay unchanged.

        The new variable must have the same name than the updated one.

        If no variable with the given name exists in the tax and benefit system, no error will be raised and the new variable will be simply added.

        :param Variable variable: Variable to add. Must be a subclass of Variable.
        """
        return self.load_variable(variable, update=True)

    def add_variables_from_file(self, file_path):
        """
        Adds all OpenFisca variables contained in a given file to the tax and benefit system.
        """
        try:
            file_name = path.splitext(path.basename(file_path))[0]

            #  As Python remembers loaded modules by name, in order to prevent collisions, we need to make sure that:
            #  - Files with the same name, but located in different directories, have a different module names. Hence the file path hash in the module name.
            #  - The same file, loaded by different tax and benefit systems, has distinct module names. Hence the `id(self)` in the module name.
            module_name = '{}_{}_{}'.format(id(self),
                                            hash(path.abspath(file_path)),
                                            file_name)

            module_directory = path.dirname(file_path)
            try:
                module = load_module(
                    module_name, *find_module(file_name, [module_directory]))
            except NameError as e:
                logging.error(
                    str(e) +
                    ": if this code used to work, this error might be due to a major change in OpenFisca-Core. Checkout the changelog to learn more: <https://github.com/openfisca/openfisca-core/blob/master/CHANGELOG.md>"
                )
                raise
            potential_variables = [
                getattr(module, item) for item in dir(module)
                if not item.startswith('__')
            ]
            for pot_variable in potential_variables:
                # We only want to get the module classes defined in this module (not imported)
                if isclass(pot_variable) and issubclass(
                        pot_variable,
                        Variable) and pot_variable.__module__ == module_name:
                    self.add_variable(pot_variable)
        except Exception:
            log.error(
                'Unable to load OpenFisca variables from file "{}"'.format(
                    file_path))
            raise

    def add_variables_from_directory(self, directory):
        """
        Recursively explores a directory, and adds all OpenFisca variables found there to the tax and benefit system.
        """
        py_files = glob.glob(path.join(directory, "*.py"))
        for py_file in py_files:
            self.add_variables_from_file(py_file)
        subdirectories = glob.glob(path.join(directory, "*/"))
        for subdirectory in subdirectories:
            self.add_variables_from_directory(subdirectory)

    def add_variables(self, *variables):
        """
        Adds a list of OpenFisca Variables to the `TaxBenefitSystem`.

        See also :any:`add_variable`
        """
        for variable in variables:
            self.add_variable(variable)

    def load_extension(self, extension):
        """
        Loads an extension to the tax and benefit system.

        :param string extension: The extension to load. Can be an absolute path pointing to an extension directory, or the name of an OpenFisca extension installed as a pip package.

        """
        if path.isdir(extension):
            if find_packages(extension):
                # Load extension from a package directory
                extension_directory = path.join(extension,
                                                find_packages(extension)[0])
            else:
                # Load extension from a simple directory
                extension_directory = extension
        else:
            # Load extension from installed pip package
            try:
                package = importlib.import_module(extension)
                extension_directory = package.__path__[0]
            except ImportError:
                message = linesep.join([
                    traceback.format_exc(),
                    'Error loading extension: `{}` is neither a directory, nor a package.'
                    .format(extension),
                    'Are you sure it is installed in your environment? If so, look at the stack trace above to determine the origin of this error.',
                    'See more at <https://github.com/openfisca/openfisca-extension-template#installing>.'
                ])
                raise ValueError(message)

        self.add_variables_from_directory(extension_directory)
        param_dir = path.join(extension_directory, 'parameters')
        if path.isdir(param_dir):
            extension_parameters = ParameterNode(directory_path=param_dir)
            self.parameters.merge(extension_parameters)

    def apply_reform(self, reform_path):
        """
        Generates a new tax and benefit system applying a reform to the tax and benefit system.

        The current tax and benefit system is **not** mutated.

        :param string reform_path: The reform to apply. Must respect the format *installed_package.sub_module.reform*

        :returns: A reformed tax and benefit system.

        Exemple:

        >>> self.apply_reform('openfisca_france.reforms.inversion_revenus')

        """
        from openfisca_core.reforms import Reform
        try:
            reform_package, reform_name = reform_path.rsplit('.', 1)
        except ValueError:
            raise ValueError(
                '`{}` does not seem to be a path pointing to a reform. A path looks like `some_country_package.reforms.some_reform.`'
                .format(reform_path).encode('utf-8'))
        try:
            reform_module = importlib.import_module(reform_package)
        except ImportError:
            message = linesep.join([
                traceback.format_exc(), 'Could not import `{}`.'.format(
                    reform_package).encode('utf-8'),
                'Are you sure of this reform module name? If so, look at the stack trace above to determine the origin of this error.'
            ])
            raise ValueError(message)
        reform = getattr(reform_module, reform_name, None)
        if reform is None:
            raise ValueError('{} has no attribute {}'.format(
                reform_package, reform_name).encode('utf-8'))
        if not issubclass(reform, Reform):
            raise ValueError(
                '`{}` does not seem to be a valid Openfisca reform.'.format(
                    reform_path).encode('utf-8'))

        return reform(self)

    def get_variable(self, variable_name, check_existence=False):
        """
        Get a variable from the tax and benefit system.

        :param variable_name: Name of the requested variable.
        :param check_existence: If True, raise an error if the requested variable does not exist.
        """
        variables = self.variables.get(variable_name)
        if not variables and check_existence:
            raise VariableNotFound(variable_name, self)
        return variables

    def neutralize_variable(self, variable_name):
        """
        Neutralizes an OpenFisca variable existing in the tax and benefit system.

        A neutralized variable always returns its default value when computed.

        Trying to set inputs for a neutralized variable has no effect except raising a warning.
        """
        self.variables[variable_name] = get_neutralized_variable(
            self.get_variable(variable_name))

    def load_parameters(self, path_to_yaml_dir):
        """
        Loads the legislation parameter for a directory containing YAML parameters files.

        :param path_to_yaml_dir: Absolute path towards the YAML parameter directory.

        Exemples:

        >>> self.load_parameters('/path/to/yaml/parameters/dir')
        """

        parameters = ParameterNode('', directory_path=path_to_yaml_dir)

        if self.preprocess_parameters is not None:
            parameters = self.preprocess_parameters(parameters)

        self.parameters = parameters

    def _get_baseline_parameters_at_instant(self, instant):
        baseline = self.baseline
        if baseline is None:
            return self.get_parameters_at_instant(instant)
        return baseline._get_baseline_parameters_at_instant(instant)

    def get_parameters_at_instant(self, instant):
        """
        Get the parameters of the legislation at a given instant

        :param instant: string of the format 'YYYY-MM-DD' or `openfisca_core.periods.Instant` instance.
        :returns: The parameters of the legislation at a given instant.
        :rtype: :any:`ParameterNodeAtInstant`
        """
        if isinstance(instant, periods.Period):
            instant = instant.start
        elif isinstance(instant, (basestring_type, int)):
            instant = periods.instant(instant)
        else:
            assert isinstance(
                instant, periods.Instant
            ), "Expected an Instant (e.g. Instant((2017, 1, 1)) ). Got: {}.".format(
                instant)

        parameters_at_instant = self._parameters_at_instant_cache.get(instant)
        if parameters_at_instant is None and self.parameters is not None:
            parameters_at_instant = self.parameters.get_at_instant(
                str(instant))
            self._parameters_at_instant_cache[instant] = parameters_at_instant
        return parameters_at_instant

    def get_package_metadata(self):
        """
            Gets metatada relative to the country package the tax and benefit system is built from.

            :returns: Country package metadata
            :rtype: dict

            Exemple:

            >>> tax_benefit_system.get_package_metadata()
            >>> {
            >>>    'location': '/path/to/dir/containing/package',
            >>>    'name': 'openfisca-france',
            >>>    'repository_url': 'https://github.com/openfisca/openfisca-france',
            >>>    'version': '17.2.0'
            >>>    }
        """
        # Handle reforms
        if self.baseline:
            return self.baseline.get_package_metadata()

        fallback_metadata = {
            'name': self.__class__.__name__,
            'version': '',
            'repository_url': '',
            'location': '',
        }

        module = inspect.getmodule(self)
        if not module.__package__:
            return fallback_metadata
        package_name = module.__package__.split('.')[0]
        try:
            distribution = pkg_resources.get_distribution(package_name)
        except pkg_resources.DistributionNotFound:
            return fallback_metadata

        location = inspect.getsourcefile(module).split(package_name)[0].rstrip(
            '/')

        home_page_metadatas = [
            metadata.split(':', 1)[1].strip(' ')
            for metadata in distribution._get_metadata(distribution.PKG_INFO)
            if 'Home-page' in metadata
        ]
        repository_url = home_page_metadatas[0] if home_page_metadatas else ''
        return {
            'name': distribution.key,
            'version': distribution.version,
            'repository_url': repository_url,
            'location': location,
        }

    def get_variables(self, entity=None):
        """
        Gets all variables contained in a tax and benefit system.

        :param <Entity subclass> entity: If set, returns only the variable defined for the given entity.

        :returns: A dictionnary, indexed by variable names.
        :rtype: dict

        """
        if not entity:
            return self.variables
        else:
            return {
                variable_name: variable
                for variable_name, variable in self.variables.items()
                if variable.entity == entity
            }

    def clone(self):
        new = empty_clone(self)
        new_dict = new.__dict__

        for key, value in self.__dict__.items():
            if key not in ('parameters', '_parameters_at_instant_cache',
                           'variables', 'open_api_config'):
                new_dict[key] = value

        new_dict['parameters'] = self.parameters.clone()
        new_dict['_parameters_at_instant_cache'] = {}
        new_dict['variables'] = self.variables.copy()
        new_dict['open_api_config'] = self.open_api_config.copy()
        return new

    def entities_plural(self):
        return {entity.plural for entity in self.entities}

    def entities_by_singular(self):
        return {entity.key: entity for entity in self.entities}
def input_to_period_tuple(value, state=None):
    """Convert an input string to a period tuple.

    .. note:: This function doesn't return a period, but a tuple that allows to construct a period.

    >>> input_to_period_tuple('2014')
    (('year', 2014), None)
    >>> input_to_period_tuple('2014:2')
    (('year', 2014, 2), None)
    >>> input_to_period_tuple('2014-2')
    (('month', (2014, 2)), None)
    >>> input_to_period_tuple('2014-3:12')
    (('month', (2014, 3), 12), None)
    >>> input_to_period_tuple('2014-2-3')
    (('day', (2014, 2, 3)), None)
    >>> input_to_period_tuple('2014-3-4:2')
    (('day', (2014, 3, 4), 2), None)

    >>> input_to_period_tuple('year:2014')
    (('year', '2014'), None)
    >>> input_to_period_tuple('year:2014:2')
    (('year', '2014', '2'), None)
    >>> input_to_period_tuple('year:2014-2:2')
    (('year', '2014-2', '2'), None)
    """
    if value is None:
        return value, None
    if state is None:
        state = conv.default_state
    split_value = tuple(
        clean_fragment for clean_fragment in (fragment.strip()
                                              for fragment in value.split(':'))
        if clean_fragment)
    if not split_value:
        return None, None
    if len(split_value) == 1:
        split_value = tuple(
            clean_fragment
            for clean_fragment in (fragment.strip()
                                   for fragment in split_value[0].split('-'))
            if clean_fragment)
        if len(split_value) == 1:
            return conv.pipe(
                conv.input_to_strict_int,
                conv.test_greater_or_equal(0),
                conv.function(lambda year: ('year', year)),
            )(split_value[0], state=state)
        if len(split_value) == 2:
            return conv.pipe(
                conv.struct((
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_greater_or_equal(0),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 12),
                    ),
                ), ),
                conv.function(lambda month_tuple: ('month', month_tuple)),
            )(split_value, state=state)
        if len(split_value) == 3:
            return conv.pipe(
                conv.struct((
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_greater_or_equal(0),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 12),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 31),
                    ),
                ), ),
                conv.function(lambda day_tuple: ('day', day_tuple)),
            )(split_value, state=state)
        return split_value, state._(
            'Instant string contains too much "-" for a year, a month or a day'
        )
    if len(split_value) == 2:
        split_start = tuple(
            clean_fragment
            for clean_fragment in (fragment.strip()
                                   for fragment in split_value[0].split('-'))
            if clean_fragment)
        size, error = conv.input_to_int(split_value[1], state=state)
        if error is None:
            if len(split_start) == 1:
                start, error = conv.pipe(
                    conv.input_to_strict_int,
                    conv.test_greater_or_equal(0),
                )(split_start[0], state=state)
                if error is None:
                    return ('year', start, size), None
            elif len(split_start) == 2:
                start, error = conv.struct((
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_greater_or_equal(0),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 12),
                    ),
                ), )(split_start, state=state)
                if error is None:
                    return ('month', start, size), None
            elif len(split_start) == 3:
                start, error = conv.struct((
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_greater_or_equal(0),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 12),
                    ),
                    conv.pipe(
                        conv.input_to_strict_int,
                        conv.test_between(1, 31),
                    ),
                ), )(split_start, state=state)
                if error is None:
                    return ('day', start, size), None
    return split_value, None
        def json_or_python_to_test_case(value, state=None):
            if value is None:
                return value, None
            if state is None:
                state = conv.default_state

            column_by_name = self.tax_benefit_system.column_by_name

            # First validation and conversion step
            test_case, error = conv.pipe(
                conv.test_isinstance(dict),
                conv.struct(
                    dict(
                        individus=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(conv.test_isinstance(dict), drop_none_items=True),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(
                                                id=conv.pipe(conv.test_isinstance((basestring, int)), conv.not_none)
                                            ).iteritems(),
                                            (
                                                (column.name, column.json_to_python)
                                                for column in column_by_name.itervalues()
                                                if column.entity == "ind" and column.name not in ("idmen", "quimen")
                                            ),
                                        )
                                    ),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.empty_to_none,
                            conv.not_none,
                        ),
                        menages=conv.pipe(
                            conv.make_item_to_singleton(),
                            conv.test_isinstance(list),
                            conv.uniform_sequence(conv.test_isinstance(dict), drop_none_items=True),
                            conv.function(scenarios.set_entities_json_id),
                            conv.uniform_sequence(
                                conv.struct(
                                    dict(
                                        itertools.chain(
                                            dict(
                                                autres=conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance((basestring, int)), drop_none_items=True
                                                    ),
                                                    conv.default([]),
                                                ),
                                                conjoint=conv.pipe(
                                                    conv.test_isinstance(basestring, int), conv.default(None)
                                                ),
                                                enfants=conv.pipe(
                                                    conv.test_isinstance(list),
                                                    conv.uniform_sequence(
                                                        conv.test_isinstance((basestring, int)), drop_none_items=True
                                                    ),
                                                    conv.default([]),
                                                ),
                                                id=conv.pipe(conv.test_isinstance((basestring, int)), conv.not_none),
                                                personne_de_reference=conv.pipe(
                                                    conv.test_isinstance(basestring, int), conv.default(None)
                                                ),
                                            ).iteritems(),
                                            (
                                                (column.name, column.json_to_python)
                                                for column in column_by_name.itervalues()
                                                if column.entity == "men"
                                            ),
                                        )
                                    ),
                                    drop_none_values=True,
                                ),
                                drop_none_items=True,
                            ),
                            conv.default({}),
                        ),
                    )
                ),
            )(value, state=state)
            if error is not None:
                return test_case, error

            # Second validation step
            menages_individus_id = [individu["id"] for individu in test_case["individus"]]
            test_case, error = conv.struct(
                dict(
                    menages=conv.uniform_sequence(
                        conv.struct(
                            dict(
                                autres=conv.uniform_sequence(conv.test_in_pop(menages_individus_id)),
                                conjoint=conv.test_in_pop(menages_individus_id),
                                enfants=conv.uniform_sequence(conv.test_in_pop(menages_individus_id)),
                                personne_de_reference=conv.test_in_pop(menages_individus_id),
                            ),
                            default=conv.noop,
                        )
                    )
                ),
                default=conv.noop,
            )(test_case, state=state)

            remaining_individus_id = set(menages_individus_id)
            if remaining_individus_id:
                individu_index_by_id = {
                    individu[u"id"]: individu_index for individu_index, individu in enumerate(test_case[u"individus"])
                }
                if error is None:
                    error = {}
                for individu_id in remaining_individus_id:
                    error.setdefault("individus", {})[individu_index_by_id[individu_id]] = state._(
                        u"Individual is missing from {}"
                    ).format(
                        state._(u" & ").join(
                            word
                            for word in [u"menages" if individu_id in menages_individus_id else None]
                            if word is not None
                        )
                    )
            if error is not None:
                return test_case, error

            return test_case, error