Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow complete group encapsulation #1070

Merged
merged 14 commits into from
Oct 26, 2021
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 35.7.0 [#1070](https://github.com/openfisca/openfisca-core/pulls/1070)

#### New Features

- Add group population shortcut to containing groups entities
bonjourmauko marked this conversation as resolved.
Show resolved Hide resolved

## 35.6.0 [#1054](https://github.com/openfisca/openfisca-core/pull/1054)

#### New Features
Expand Down
24 changes: 21 additions & 3 deletions openfisca_core/entities/group_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@


class GroupEntity(Entity):
"""
Represents an entity composed of several persons with different roles, on which calculations are run.
"""Represents an entity containing several others with different roles.

A :class:`.GroupEntity` represents an :class:`.Entity` containing
several other :class:`.Entity` with different :class:`.Role`, and on
which calculations can be run.

Args:
key: A key to identify the group entity.
plural: The ``key``, pluralised.
label: A summary description.
doc: A full description.
roles: The list of :class:`.Role` of the group entity.
containing_entities: The list of keys of group entities whose members
are guaranteed to be a superset of this group's entities.

.. versionchanged:: 35.7.0
Added ``containing_entities``, that allows the defining of group
entities which entirely contain other group entities.

"""

def __init__(self, key, plural, label, doc, roles):
def __init__(self, key, plural, label, doc, roles, containing_entities = ()):
bonjourmauko marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(key, plural, label, doc)
self.roles_description = roles
self.roles = []
Expand All @@ -23,3 +40,4 @@ def __init__(self, key, plural, label, doc, roles):
role.max = len(role.subroles)
self.flattened_roles = sum([role2.subroles or [role2] for role2 in self.roles], [])
self.is_person = False
self.containing_entities = containing_entities
4 changes: 2 additions & 2 deletions openfisca_core/entities/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from openfisca_core import entities


def build_entity(key, plural, label, doc = "", roles = None, is_person = False, class_override = None):
def build_entity(key, plural, label, doc = "", roles = None, is_person = False, class_override = None, containing_entities = ()):
if is_person:
return entities.Entity(key, plural, label, doc)
else:
return entities.GroupEntity(key, plural, label, doc, roles)
return entities.GroupEntity(key, plural, label, doc, roles, containing_entities = containing_entities)
2 changes: 2 additions & 0 deletions openfisca_core/projectors/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ def get_projector_from_shortcut(population, shortcut, parent = None):
role = next((role for role in population.entity.flattened_roles if (role.max == 1) and (role.key == shortcut)), None)
if role:
return projectors.UniqueRoleToEntityProjector(population, role, parent)
if shortcut in population.entity.containing_entities:
bonjourmauko marked this conversation as resolved.
Show resolved Hide resolved
return getattr(projectors.FirstPersonToEntityProjector(population, parent), shortcut)
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
; E501: We do not enforce a maximum line length.
; F403/405: We ignore * imports.
; R0401: We avoid cyclic imports —required for unit/doc tests.
; RST301: We use Google Python Style (see https://pypi.org/project/flake8-rst-docstrings/)
; W503/504: We break lines before binary operators (Knuth's style).

[flake8]
extend-ignore = D
hang-closing = true
ignore = E128,E251,F403,F405,E501,W503,W504
ignore = E128,E251,F403,F405,E501,RST301,W503,W504
in-place = true
include-in-doctest = openfisca_core/commons openfisca_core/types
rst-directives = attribute, deprecated, seealso, versionadded, versionchanged
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

setup(
name = 'OpenFisca-Core',
version = '35.6.0',
version = '35.7.0',
author = 'OpenFisca Team',
author_email = '[email protected]',
classifiers = [
Expand Down
86 changes: 86 additions & 0 deletions tests/core/test_formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,89 @@ def test_compare_multiplication_and_switch(simulation, month):
uses_multiplication = simulation.calculate('uses_multiplication', period = month)
uses_switch = simulation.calculate('uses_switch', period = month)
assert numpy.all(uses_switch == uses_multiplication)


def test_group_encapsulation():
bonjourmauko marked this conversation as resolved.
Show resolved Hide resolved
"""Projects a calculation to all members of an entity.

When a household contains more than one family
Variables can be defined for the the household
And calculations are projected to all the member families.

"""
from openfisca_core.taxbenefitsystems import TaxBenefitSystem
from openfisca_core.entities import build_entity
from openfisca_core.periods import ETERNITY

person_entity = build_entity(
key="person",
plural="people",
label="A person",
is_person=True,
)
family_entity = build_entity(
key="family",
plural="families",
label="A family (all members in the same household)",
containing_entities=["household"],
roles=[{
"key": "member",
"plural": "members",
"label": "Member",
}]
)
household_entity = build_entity(
key="household",
plural="households",
label="A household, containing one or more families",
roles=[{
"key": "member",
"plural": "members",
"label": "Member",
}]
)

entities = [person_entity, family_entity, household_entity]

system = TaxBenefitSystem(entities)

class household_level_variable(Variable):
value_type = int
entity = household_entity
definition_period = ETERNITY

class projected_family_level_variable(Variable):
value_type = int
entity = family_entity
definition_period = ETERNITY

def formula(family, period):
return family.household("household_level_variable", period)

system.add_variables(household_level_variable, projected_family_level_variable)

simulation = SimulationBuilder().build_from_dict(system, {
"people": {
"person1": {},
"person2": {},
"person3": {}
},
"families": {
"family1": {
"members": ["person1", "person2"]
},
"family2": {
"members": ["person3"]
},
},
"households": {
"household1": {
"members": ["person1", "person2", "person3"],
"household_level_variable": {
"eternity": 5
}
}
}
})

assert (simulation.calculate("projected_family_level_variable", "2021-01-01") == 5).all()
Loading