Skip to content

Commit

Permalink
Merge pull request #147 from abhcs/fix-issue-139
Browse files Browse the repository at this point in the history
Raise an exception during construction if the variable lacks a label or if it has a formula and an add/subtract
  • Loading branch information
MaxGhenis authored Feb 6, 2024
2 parents 9994ced + 0d562eb commit 8fcd49e
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 0 deletions.
5 changes: 5 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- bump: minor
changes:
changed:
- A variable must have a label.
- If a variable has formulas then it must not have adds/subtracts, and vice versa.
15 changes: 15 additions & 0 deletions policyengine_core/variables/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ def __init__(self, baseline_variable=None):
self.label = self.set(
attr, "label", allowed_type=str, setter=self.set_label
)

if self.label is None:
raise ValueError(
'Variable "{name}" has no label'.format(name=self.name)
)

self.end = self.set(attr, "end", allowed_type=str, setter=self.set_end)
self.reference = self.set(attr, "reference", setter=self.set_reference)
self.cerfa_field = self.set(
Expand Down Expand Up @@ -286,6 +292,15 @@ def __init__(self, baseline_variable=None):
)
)

if len(self.formulas) != 0 and (
self.adds is not None or self.subtracts is not None
):
raise ValueError(
'Variable "{name}" has a formula and an add or subtract'.format(
name=self.name
)
)

self.is_neutralized = False

# ----- Setters used to build the variable ----- #
Expand Down
3 changes: 3 additions & 0 deletions tests/core/parameters/operations/test_nesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class country(Variable):
definition_period = ETERNITY
possible_values = Country
default_value = Country.ENGLAND
label = "country"

class Region(Enum):
NORTH_EAST = "North East"
Expand All @@ -60,11 +61,13 @@ class region(Variable):
definition_period = ETERNITY
possible_values = Region
default_value = Region.NORTH_EAST
label = "region"

class family_size(Variable):
value_type = int
entity = Person
definition_period = ETERNITY
label = "family size"

from policyengine_core.parameters import homogenize_parameter_structures
from policyengine_core.taxbenefitsystems import TaxBenefitSystem
Expand Down
3 changes: 3 additions & 0 deletions tests/core/test_calculate_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ class simple_variable(Variable):
entity = entities.Person
definition_period = periods.MONTH
value_type = int
label = "simple variable"


class variable_with_calculate_output_add(Variable):
entity = entities.Person
definition_period = periods.MONTH
value_type = int
calculate_output = simulations.calculate_output_add
label = "variable with calculate_output_add"


class variable_with_calculate_output_divide(Variable):
entity = entities.Person
definition_period = periods.YEAR
value_type = int
calculate_output = simulations.calculate_output_divide
label = "variable with calculate_output_divide"


@pytest.fixture(scope="module", autouse=True)
Expand Down
8 changes: 8 additions & 0 deletions tests/core/test_cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class variable1(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 1"

def formula(person, period):
return person("variable2", period)
Expand All @@ -31,6 +32,7 @@ class variable2(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 2"

def formula(person, period):
return person("variable1", period)
Expand All @@ -41,6 +43,7 @@ class variable3(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 3"

def formula(person, period):
return person("variable4", period.last_month)
Expand All @@ -50,6 +53,7 @@ class variable4(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 4"

def formula(person, period):
return person("variable3", period)
Expand All @@ -61,6 +65,7 @@ class variable5(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 5"

def formula(person, period):
variable6 = person("variable6", period.last_month)
Expand All @@ -71,6 +76,7 @@ class variable6(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 6"

def formula(person, period):
variable5 = person("variable5", period)
Expand All @@ -81,6 +87,7 @@ class variable7(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "variable 7"

def formula(person, period):
variable5 = person("variable5", period)
Expand All @@ -92,6 +99,7 @@ class cotisation(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "cotisation"

def formula(person, period):
if period.start.month == 12:
Expand Down
6 changes: 6 additions & 0 deletions tests/core/test_formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ class choice(Variable):
value_type = int
entity = entities.Person
definition_period = periods.MONTH
label = "choice"


class uses_multiplication(Variable):
value_type = int
entity = entities.Person
label = "Variable with formula that uses multiplication"
definition_period = periods.MONTH
label = "uses multiplication"

def formula(person, period):
choice = person("choice", period)
Expand All @@ -30,6 +32,7 @@ class returns_scalar(Variable):
entity = entities.Person
label = "Variable with formula that returns a scalar value"
definition_period = periods.MONTH
label = "returns scalar"

def formula(person, period):
return 666
Expand All @@ -40,6 +43,7 @@ class uses_switch(Variable):
entity = entities.Person
label = "Variable with formula that uses switch"
definition_period = periods.MONTH
label = "uses switch"

def formula(person, period):
choice = person("choice", period)
Expand Down Expand Up @@ -154,11 +158,13 @@ class household_level_variable(Variable):
value_type = int
entity = household_entity
definition_period = ETERNITY
label = "household level variable"

class projected_family_level_variable(Variable):
value_type = int
entity = family_entity
definition_period = ETERNITY
label = "projected family level variable"

def formula(family, period):
return family.household("household_level_variable", period)
Expand Down
7 changes: 7 additions & 0 deletions tests/core/test_projectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,15 @@ class household_enum_variable(Variable):
default_value = enum.FIRST_OPTION
entity = household
definition_period = ETERNITY
label = "household enum variable"

class projected_enum_variable(Variable):
value_type = Enum
possible_values = enum
default_value = enum.FIRST_OPTION
entity = person
definition_period = ETERNITY
label = "projected enum variable"

def formula(person, period):
return person.household("household_enum_variable", period)
Expand Down Expand Up @@ -210,6 +212,7 @@ class household_projected_variable(Variable):
default_value = enum.FIRST_OPTION
entity = household
definition_period = ETERNITY
label = "household projected variable"

def formula(household, period):
return household.value_from_first_person(
Expand All @@ -222,6 +225,7 @@ class person_enum_variable(Variable):
default_value = enum.FIRST_OPTION
entity = person
definition_period = ETERNITY
label = "person enum variable"

system.add_variables(household_projected_variable, person_enum_variable)

Expand Down Expand Up @@ -303,13 +307,15 @@ class household_level_variable(Variable):
default_value = enum.FIRST_OPTION
entity = household_entity
definition_period = ETERNITY
label = "household level variable"

class projected_family_level_variable(Variable):
value_type = Enum
possible_values = enum
default_value = enum.FIRST_OPTION
entity = family_entity
definition_period = ETERNITY
label = "projected family level variable"

def formula(family, period):
return family.household("household_level_variable", period)
Expand All @@ -318,6 +324,7 @@ class decoded_projected_family_level_variable(Variable):
value_type = str
entity = family_entity
definition_period = ETERNITY
label = "decoded projected family level variable"

def formula(family, period):
return family.household(
Expand Down
3 changes: 3 additions & 0 deletions tests/core/test_simulation_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class intvar(Variable):
definition_period = periods.ETERNITY
value_type = int
entity = persons
label = "int variable"

def __init__(self):
super().__init__()
Expand All @@ -32,6 +33,7 @@ class datevar(Variable):
definition_period = periods.ETERNITY
value_type = datetime.date
entity = persons
label = "date variable"

def __init__(self):
super().__init__()
Expand All @@ -50,6 +52,7 @@ class TestEnum(Variable):
set_input = None
possible_values = Enum("foo", "bar")
name = "enum"
label = "enum variable"

def __init__(self):
pass
Expand Down
1 change: 1 addition & 0 deletions tests/core/variables/test_annualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class monthly_variable(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "monthly variable"

def formula(person, period, parameters):
variable.calculation_count += 1
Expand Down
100 changes: 100 additions & 0 deletions tests/core/variables/test_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,103 @@ class variable_with_strange_attr(Variable):

with raises(ValueError):
tax_benefit_system.add_variable(variable_with_strange_attr)


class variable__one_formula_one_add(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "Variable with one formula and one add."
adds = ["pass"]

def formula():
pass


def test_one_formula_one_add():
check_error_at_add_variable(
tax_benefit_system,
variable__one_formula_one_add,
'Variable "{name}" has a formula and an add or subtract'.format(
name="variable__one_formula_one_add"
),
)


class variable__one_formula_one_subtract(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "Variable with one formula and one subtract."
adds = ["pass"]

def formula():
pass


def test_one_formula_one_subtract():
check_error_at_add_variable(
tax_benefit_system,
variable__one_formula_one_subtract,
'Variable "{name}" has a formula and an add or subtract'.format(
name="variable__one_formula_one_subtract"
),
)


class variable__one_formula(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "Variable with one formula."

def formula():
pass


def test_one_formula():
tax_benefit_system.add_variable(variable__one_formula)
variable = tax_benefit_system.variables["variable__one_formula"]
assert len(variable.formulas)


class variable__one_add(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "Variable with one add."
adds = ["pass"]


def test_one_add():
tax_benefit_system.add_variable(variable__one_add)
variable = tax_benefit_system.variables["variable__one_add"]
assert len(variable.adds)


class variable__one_subtract(Variable):
value_type = int
entity = Person
definition_period = MONTH
label = "Variable with one subtract."
subtracts = ["pass"]


def test_one_subtract():
tax_benefit_system.add_variable(variable__one_subtract)
variable = tax_benefit_system.variables["variable__one_subtract"]
assert len(variable.subtracts)


class variable__no_label(Variable):
value_type = int
entity = Person
definition_period = MONTH


def test_no_label():
check_error_at_add_variable(
tax_benefit_system,
variable__no_label,
'Variable "{name}" has no label'.format(name="variable__no_label"),
)
1 change: 1 addition & 0 deletions tests/fixtures/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
class TestVariable(Variable):
definition_period = periods.ETERNITY
value_type = float
label = "test variable"

def __init__(self, entity):
self.__class__.entity = entity
Expand Down

0 comments on commit 8fcd49e

Please sign in to comment.