From a9aa1b6e116355b8305dc25f77f2435d062826f5 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Sep 2023 03:47:42 +0200 Subject: [PATCH 01/12] Calculate abatement rate --- .../dictionary/income_test_1.yaml | 66 +++++++++++++++++++ .../dictionary/test_abatement_rate.py | 33 ++++++++++ openfisca_aotearoa/variables/__init__.py | 0 openfisca_aotearoa/variables/acts/__init__.py | 0 .../acts/social_security/__init__.py | 0 .../social_security/dictionary/__init__.py | 3 + .../dictionary/abatement_rate.py | 36 ++++++++++ .../jobseeker_support__benefit.py | 1 + 8 files changed, 139 insertions(+) create mode 100644 openfisca_aotearoa/parameters/social_security/dictionary/income_test_1.yaml create mode 100644 openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py create mode 100644 openfisca_aotearoa/variables/__init__.py create mode 100644 openfisca_aotearoa/variables/acts/__init__.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/__init__.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py diff --git a/openfisca_aotearoa/parameters/social_security/dictionary/income_test_1.yaml b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_1.yaml new file mode 100644 index 00000000..2ce7a5e6 --- /dev/null +++ b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_1.yaml @@ -0,0 +1,66 @@ +description: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 1 - + The applicable rate of benefit must be reduced— + (a) by 30 cents for every $1 of the total income of the beneficiary and + the beneficiary’s spouse or partner that is more than $160 a week but + not more than $250 a week + (b) by 70 cents for every $1 of that income that is more than $250 a week +metadata: + type: marginal_rate + rate_unit: /1 + threshold_unit: currency-NZD + reference: + 2018-11-26: + title: Social Security Act 2018 (as enacted) + href: https://www.legislation.govt.nz/act/public/2018/0032/59.0/DLM6784375.html#DLM6784553 + 2020-04-01: + title: Social Security Act 2018 + href: https://www.legislation.govt.nz/regulation/public/2020/0014/latest/whole.html#LMS311980 + 2021-04-01: + title: Social Security Act 2018 + href: https://www.legislation.govt.nz/regulation/public/2021/0016/latest/whole.html#LMS453381 +brackets: + - rate: + 2018-11-26: + value: 0.0 + 2020-04-01: + value: 0.0 + 2021-04-01: + value: 0.0 + threshold: + 2018-11-26: + value: 0 + 2020-04-01: + value: 0 + 2021-04-01: + value: 0 + - rate: + 2018-11-26: + value: 0.3 + 2020-04-01: + value: 0.3 + 2021-04-01: + value: 0.3 + threshold: + 2018-11-26: + value: 100 + 2020-04-01: + value: 115 + 2021-04-01: + value: 160 + - rate: + 2018-11-26: + value: 0.7 + 2020-04-01: + value: 0.7 + 2021-04-01: + value: 0.7 + threshold: + 2018-11-26: + value: 200 + 2020-04-01: + value: 215 + 2021-04-01: + value: 250 diff --git a/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py b/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py new file mode 100644 index 00000000..17114de9 --- /dev/null +++ b/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py @@ -0,0 +1,33 @@ +import numpy +import pytest + +from openfisca_core import taxscales, tools + +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class IncomeTest(taxscales.MarginalRateTaxScale): + ... + + +@pytest.fixture +def income_test() -> IncomeTest: + scale = IncomeTest() + scale.add_bracket(0, 0.00) + scale.add_bracket(160, 0.30) + scale.add_bracket(250, 0.70) + return scale + + +def test_abatement_rate_floor(income_test: IncomeTest) -> None: + rate = numpy.full((4,), 100.00) + income = numpy.array([159.99, 160.00, 160.99, 161.99]) + abatement = dictionary.AbatementRate(rate, income)(income_test.calc) + tools.assert_near(abatement, numpy.array([0.00, 0.00, 0.00, 0.30])) + + +def test_income_test_ceiling(income_test: IncomeTest) -> None: + rate = numpy.full((4,), 100.00) + income = numpy.array([249.99, 250.00, 251.00, 251.99]) + abatement = dictionary.AbatementRate(rate, income)(income_test.calc) + tools.assert_near(abatement, numpy.array([26.70, 27.00, 27.70, 27.70])) diff --git a/openfisca_aotearoa/variables/__init__.py b/openfisca_aotearoa/variables/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openfisca_aotearoa/variables/acts/__init__.py b/openfisca_aotearoa/variables/acts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openfisca_aotearoa/variables/acts/social_security/__init__.py b/openfisca_aotearoa/variables/acts/social_security/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py new file mode 100644 index 00000000..6eb6b78b --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py @@ -0,0 +1,3 @@ +from .abatement_rate import AbatementRate + +__all__ = ["AbatementRate"] diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py new file mode 100644 index 00000000..2be72c01 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py @@ -0,0 +1,36 @@ +from numpy.typing import NDArray +from typing import Callable + +import dataclasses + +import numpy + +Vector = NDArray[numpy.float_] + + +@dataclasses.dataclass +class AbatementRate: + """Abatement rate applicable to income-tested benefits. + + Income test means that the applicable rate of a benefit must be reduced by + a certain amount for each unit of income defined by the policy-maker, above + a certain floor but below a certain ceiling. + + Abatement rate is the application of an income test to a specific benefit, + taking into account that benefit's own definition of total income; that is, + "the rate at which a rate of benefit [...] must, under the appropriate + income test, be reduced on account of income." + + .. see:: https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375 + + """ + + #: Applicable rate of benefit to be reduced. + applicable_rate: Vector + + #: Total income of the beneficiary and the beneficiary’s spouse or partner. + total_income: Vector + + def __call__(self, income_test: Callable[[Vector], Vector]) -> Vector: + """Apply an income test to a benefit's applicable rate.""" + return income_test(numpy.floor(self.total_income)) diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py index 39993465..3d2e510b 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py @@ -58,6 +58,7 @@ def formula_2018_11_26(people, period, parameters): scale_1 = parameters(period).social_security.income_test_1 scale_3 = parameters(period).social_security.income_test_3b scale_4 = parameters(period).social_security.income_test_4 + return people("jobseeker_support__entitled", period) * \ ( (scale_1.calc(family_income) * test_1) + (scale_3.calc(family_income) * test_3) + (scale_4.calc(family_income) * test_4) From 65be7eb45c8a5e82f8b8eb64d981f4de6d72ba7b Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Sep 2023 03:59:56 +0200 Subject: [PATCH 02/12] Make sure abatement is not greater that benefit --- .../dictionary/test_abatement_rate.py | 9 ++++++++- .../social_security/dictionary/abatement_rate.py | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py b/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py index 17114de9..7b469983 100644 --- a/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py +++ b/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py @@ -26,8 +26,15 @@ def test_abatement_rate_floor(income_test: IncomeTest) -> None: tools.assert_near(abatement, numpy.array([0.00, 0.00, 0.00, 0.30])) -def test_income_test_ceiling(income_test: IncomeTest) -> None: +def test_abatement_rate_ceiling(income_test: IncomeTest) -> None: rate = numpy.full((4,), 100.00) income = numpy.array([249.99, 250.00, 251.00, 251.99]) abatement = dictionary.AbatementRate(rate, income)(income_test.calc) tools.assert_near(abatement, numpy.array([26.70, 27.00, 27.70, 27.70])) + + +def test_abatement_rate_is_not_negative(income_test: IncomeTest) -> None: + rate = numpy.full((3,), 100.00) + income = numpy.array([354.00, 355.00, 356.00]) + abatement = dictionary.AbatementRate(rate, income)(income_test.calc) + tools.assert_near(abatement, numpy.array([99.80, 100.00, 100.00])) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py index 2be72c01..b56011af 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py @@ -8,7 +8,7 @@ Vector = NDArray[numpy.float_] -@dataclasses.dataclass +@dataclasses.dataclass(frozen=True) class AbatementRate: """Abatement rate applicable to income-tested benefits. @@ -33,4 +33,12 @@ class AbatementRate: def __call__(self, income_test: Callable[[Vector], Vector]) -> Vector: """Apply an income test to a benefit's applicable rate.""" - return income_test(numpy.floor(self.total_income)) + + # numpy.floor is required for income tests as i.e. "35c for every $1". + floor = numpy.floor(self.total_income) + + # The abatement rate regardless of benefit rate. + abatement_rate = income_test(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, self.applicable_rate) From abb45d7a75795fef83ab5e5e08cc633eafa11140 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Sep 2023 04:34:34 +0200 Subject: [PATCH 03/12] Make income test generic --- .../{ => dictionary}/income_test_2.yaml | 17 ++- .../{ => dictionary}/income_test_3a.yaml | 0 .../{ => dictionary}/income_test_3b.yaml | 23 ++-- .../{ => dictionary}/income_test_4.yaml | 15 ++- .../social_security/income_test_1.yaml | 49 --------- .../jobseeker_support__benefit.py | 104 +++++++++++------- .../sole_parent_support_benefit.py | 15 +-- 7 files changed, 103 insertions(+), 120 deletions(-) rename openfisca_aotearoa/parameters/social_security/{ => dictionary}/income_test_2.yaml (65%) rename openfisca_aotearoa/parameters/social_security/{ => dictionary}/income_test_3a.yaml (100%) rename openfisca_aotearoa/parameters/social_security/{ => dictionary}/income_test_3b.yaml (55%) rename openfisca_aotearoa/parameters/social_security/{ => dictionary}/income_test_4.yaml (68%) delete mode 100644 openfisca_aotearoa/parameters/social_security/income_test_1.yaml diff --git a/openfisca_aotearoa/parameters/social_security/income_test_2.yaml b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_2.yaml similarity index 65% rename from openfisca_aotearoa/parameters/social_security/income_test_2.yaml rename to openfisca_aotearoa/parameters/social_security/dictionary/income_test_2.yaml index ef24ea78..c95c8a6d 100644 --- a/openfisca_aotearoa/parameters/social_security/income_test_2.yaml +++ b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_2.yaml @@ -1,9 +1,16 @@ -description: Social Security Income Test 2 +description: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 2 - + The applicable rate of benefit must be reduced— + (a) by 15 cents for every $1 of the total income of the beneficiary and + the beneficiary’s spouse or partner that is more than $160 a week but + not more than $250 a week; and + (b) by 35 cents for every $1 of that income that is more than $250 a week metadata: -# currency or /1 (rate) or year (never used) - unit: currency -# single_amount or marginal_rate or marginal_amount or value? - type: value + type: marginal_rate + rate_unit: /1 + threshold_unit: currency-NZD reference: 2018-11-26: title: Social Security Act 2018 (as enacted) diff --git a/openfisca_aotearoa/parameters/social_security/income_test_3a.yaml b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_3a.yaml similarity index 100% rename from openfisca_aotearoa/parameters/social_security/income_test_3a.yaml rename to openfisca_aotearoa/parameters/social_security/dictionary/income_test_3a.yaml diff --git a/openfisca_aotearoa/parameters/social_security/income_test_3b.yaml b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_3b.yaml similarity index 55% rename from openfisca_aotearoa/parameters/social_security/income_test_3b.yaml rename to openfisca_aotearoa/parameters/social_security/dictionary/income_test_3b.yaml index 2fa602cc..d3e82c19 100644 --- a/openfisca_aotearoa/parameters/social_security/income_test_3b.yaml +++ b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_3b.yaml @@ -1,9 +1,15 @@ -description: Social Security Income Test 3b +description: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 3 b - + The applicable rate of benefit must be reduced by 70 cents for every $1 of + total income of the beneficiary and the beneficiary’s spouse or partner + that is more than,— [...] + (b) in any other case, $160 a week metadata: -# currency or /1 (rate) or year (never used) - unit: currency -# single_amount or marginal_rate or marginal_amount or value? - type: value + type: marginal_rate + rate_unit: /1 + threshold_unit: currency-NZD reference: 2018-11-26: title: Social Security Act 2018 (as enacted) @@ -31,10 +37,3 @@ brackets: value: 90 2021-04-01: value: 160 -# Income Test 3 means that the applicable rate of benefit must be reduced by 70 -# cents for every $1 of total income of the beneficiary and the beneficiary’s -# spouse or partner that is more than,— -# (a) if the rate of benefit is a rate of New Zealand superannuation stated in -# clause 1 of Part 2 of Schedule 1 of the New Zealand Superannuation and -# Retirement Income Act 2001, $160 a week; or -# (b) in any other case, $160 a week diff --git a/openfisca_aotearoa/parameters/social_security/income_test_4.yaml b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_4.yaml similarity index 68% rename from openfisca_aotearoa/parameters/social_security/income_test_4.yaml rename to openfisca_aotearoa/parameters/social_security/dictionary/income_test_4.yaml index ac10cea3..077b92f1 100644 --- a/openfisca_aotearoa/parameters/social_security/income_test_4.yaml +++ b/openfisca_aotearoa/parameters/social_security/dictionary/income_test_4.yaml @@ -1,9 +1,14 @@ -description: Social Security Income Test 4 +description: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 4 - + The applicable rate of benefit must be reduced by 35 cents for every $1 of + the total income of the beneficiary and the beneficiary’s spouse or partner + that is more than $160 a week metadata: -# currency or /1 (rate) or year (never used) - unit: currency -# single_amount or marginal_rate or marginal_amount or value? - type: value + type: marginal_rate + rate_unit: /1 + threshold_unit: currency-NZD reference: 2018-11-26: title: Social Security Act 2018 (as enacted) diff --git a/openfisca_aotearoa/parameters/social_security/income_test_1.yaml b/openfisca_aotearoa/parameters/social_security/income_test_1.yaml deleted file mode 100644 index e48062a4..00000000 --- a/openfisca_aotearoa/parameters/social_security/income_test_1.yaml +++ /dev/null @@ -1,49 +0,0 @@ -description: Social Security Income Test 1 -metadata: -# currency or /1 (rate) or year (never used) - unit: currency -# single_amount or marginal_rate or marginal_amount or value? - type: value - reference: - 2018-11-26: - title: Social Security Act 2018 (as enacted) - href: https://www.legislation.govt.nz/act/public/2018/0032/59.0/DLM6784375.html#DLM6784553 - 2020-04-01: - title: Social Security Act 2018 - href: https://www.legislation.govt.nz/regulation/public/2020/0014/latest/whole.html#LMS311980 - 2021-04-01: - title: Social Security Act 2018 - href: https://www.legislation.govt.nz/regulation/public/2021/0016/latest/whole.html#LMS453381 -brackets: - - rate: - 2018-11-26: - value: 0 - threshold: - 2018-11-26: - value: 0 - - rate: - 2018-11-26: - value: 0.3 - threshold: - 2018-11-26: - value: 100 - 2020-04-01: - value: 115 - 2021-04-01: - value: 160 - # no change in 2022 - # no change in 2023 - # https://www.legislation.govt.nz/all/results.aspx?search=ad_act%40bill%40regulation%40deemedreg__%22Income+Test+1%22____25_ac%40bc%40rc%40dc%40apub%40aloc%40apri%40apro%40aimp%40bgov%40bloc%40bpri%40bmem%40rpub%40rimp_ac%40bc%40rc%40ainf%40anif%40bcur%40rinf%40rnif%40raif%40rasm%40rrev_a_aw_se_&p=1 - - rate: - 2018-11-26: - value: 0.7 - threshold: - 2018-11-26: - value: 200 - 2020-04-01: - value: 215 - 2021-04-01: - value: 250 - # no change in 2022 - # no change in 2023 - # https://www.legislation.govt.nz/all/results.aspx?search=ad_act%40bill%40regulation%40deemedreg__%22Income+Test+1%22____25_ac%40bc%40rc%40dc%40apub%40aloc%40apri%40apro%40aimp%40bgov%40bloc%40bpri%40bmem%40rpub%40rimp_ac%40bc%40rc%40ainf%40anif%40bcur%40rinf%40rnif%40raif%40rasm%40rrev_a_aw_se_&p=1 diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py index 3d2e510b..b59d2643 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py @@ -16,6 +16,7 @@ # For more information on OpenFisca's `entities`: # https://openfisca.org/doc/key-concepts/person,_entities,_role.html from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary class jobseeker_support__benefit(variables.Variable): @@ -37,57 +38,76 @@ class jobseeker_support__reduction(variables.Variable): reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" def formula_2018_11_26(people, period, parameters): - family_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - - # numpy.floor required for income tests as it's "35c for every $1" - family_income = numpy.floor(family_income) - - test_1 = people("schedule_4__part1_1_c", period) + \ - people("schedule_4__part1_1_e", period) + \ - people("schedule_4__part1_1_f", period) - - test_3 = people("schedule_4__part1_1_a", period) + \ - people("schedule_4__part1_1_b", period) + \ - people("schedule_4__part1_1_d", period) + \ - people("schedule_4__part1_1_i", period) + \ - people("schedule_4__part1_1_j", period) + case_1 = ( + + people("schedule_4__part1_1_c", period) + + people("schedule_4__part1_1_e", period) + + people("schedule_4__part1_1_f", period) + ) - test_4 = people("schedule_4__part1_1_g", period) + \ - people("schedule_4__part1_1_h", period) + case_3 = ( + + people("schedule_4__part1_1_a", period) + + people("schedule_4__part1_1_b", period) + + people("schedule_4__part1_1_d", period) + + people("schedule_4__part1_1_i", period) + + people("schedule_4__part1_1_j", period) + ) - scale_1 = parameters(period).social_security.income_test_1 - scale_3 = parameters(period).social_security.income_test_3b - scale_4 = parameters(period).social_security.income_test_4 + case_4 = ( + + people("schedule_4__part1_1_g", period) + + people("schedule_4__part1_1_h", period) + ) - return people("jobseeker_support__entitled", period) * \ - ( - (scale_1.calc(family_income) * test_1) + (scale_3.calc(family_income) * test_3) + (scale_4.calc(family_income) * test_4) + applicable_rate = people("jobseeker_support__base", period) + total_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) + abatement = dictionary.AbatementRate(applicable_rate, total_income) + + income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc + income_test_3 = parameters(period).social_security.dictionary.income_test_3b.calc + income_test_4 = parameters(period).social_security.dictionary.income_test_4.calc + + return ( + + people("jobseeker_support__entitled", period) + * ( + + case_1 * abatement(income_test_1) + + case_3 * abatement(income_test_3) + + case_4 * abatement(income_test_4) + ) ) def formula_2020_11_09(people, period, parameters): - family_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - - # numpy.floor required for income tests as it's "35c for every $1" - family_income = numpy.floor(family_income) - - test_1 = people("schedule_4__part1_1_c", period) + \ - people("schedule_4__part1_1_e", period) + \ - people("schedule_4__part1_1_f", period) + case_1 = ( + + people("schedule_4__part1_1_c", period) + + people("schedule_4__part1_1_e", period) + + people("schedule_4__part1_1_f", period) + ) - test_3 = people("schedule_4__part1_1_a", period) + \ - people("schedule_4__part1_1_b", period) + \ - people("schedule_4__part1_1_d", period) + \ - people("schedule_4__part1_1_j", period) + case_3 = ( + + people("schedule_4__part1_1_a", period) + + people("schedule_4__part1_1_b", period) + + people("schedule_4__part1_1_d", period) + + people("schedule_4__part1_1_j", period) + ) - test_4 = people("schedule_4__part1_1_g", period) + \ - people("schedule_4__part1_1_h", period) + case_4 = ( + + people("schedule_4__part1_1_g", period) + + people("schedule_4__part1_1_h", period) + ) - scale_1 = parameters(period).social_security.income_test_1 - scale_3 = parameters(period).social_security.income_test_3b - scale_4 = parameters(period).social_security.income_test_4 - return people("jobseeker_support__entitled", period) * \ - ( - (scale_1.calc(family_income) * test_1) + (scale_3.calc(family_income) * test_3) + (scale_4.calc(family_income) * test_4) + applicable_rate = people("jobseeker_support__base", period) + total_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) + abatement = dictionary.AbatementRate(applicable_rate, total_income) + + income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc + income_test_3 = parameters(period).social_security.dictionary.income_test_3b.calc + income_test_4 = parameters(period).social_security.dictionary.income_test_4.calc + + return ( + + people("jobseeker_support__entitled", period) + * ( + + case_1 * abatement(income_test_1) + + case_3 * abatement(income_test_3) + + case_4 * abatement(income_test_4) + ) ) diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index a3347f04..e775a270 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -16,6 +16,7 @@ # For more information on OpenFisca's `entities`: # https://openfisca.org/doc/key-concepts/person,_entities,_role.html from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary class sole_parent_support__benefit(variables.Variable): @@ -52,15 +53,15 @@ class sole_parent_support__reduction(variables.Variable): reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" def formula_2018_11_26(people, period, parameters): + applicable_rate = people("sole_parent_support__base", period) family_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - childcare_deduction_limit = parameters(period.first_day).social_security.sole_parent_support.childcare_deduction_limit - family_income = family_income - numpy.clip(people("sole_parent_support__weekly_childcare_cost", period), 0, childcare_deduction_limit) - # numpy.floor required for income tests as it's "35c for every $1" - family_income = numpy.floor(family_income) - - scale_1 = parameters(period).social_security.income_test_1 - return people("sole_parent_support__entitled", period) * scale_1.calc(family_income) + total_income = family_income - numpy.clip(people("sole_parent_support__weekly_childcare_cost", period), 0, childcare_deduction_limit) + income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc + return ( + + people("sole_parent_support__entitled", period) + * dictionary.AbatementRate(applicable_rate, total_income)(income_test_1) + ) class sole_parent_support__weekly_childcare_cost(variables.Variable): From 0194c4fd80c6d6c3879a2ca9ae561462ab64d813 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Sep 2023 04:57:34 +0200 Subject: [PATCH 04/12] Version bump --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c313af8..a166a5fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## 20.2.0 - [59](https://github.com/digitalaotearoa/openfisca-aotearoa/pull/59) + +* Tax and benefit system evolution. +* Impacted periods: all. +* Impacted areas: `sole_parewnt_support`, `jobseeker_support` +* Details: + - Add `abatement_rate` as the result of applying an income test to a benefit + +#### Discussion + +Main benefits are income-tested (subject to an abatement rate based on income). +Non-main benefits can be income-tested via main benefits (i.e. accommodation +supplement via jobseeker). However, some benefits change the definitions of +certain things, i.e. income is not the same for accommodation supplement and +for jobseeker support. + +This poses a challenge to the rule-maker, because in order to define a general +rule for an income test, that applies to benefits with different definitions of +income, it has to make it _high order_. In plain English, it has to create a +_rule factory_. Something that creates a rule, that just then can then be +applied. + +There are several ways to solve this problem, yet all gravitate around two +philosophies: + +1. Stick to the letter: if the rule-maker defines a _pure high-order function_ + that can't be applied directly, just model it as close to the letter that is + possible. + +2. Incorporate: if the rule can't be applied directly, ignore it, and + incorporate it as many times as is used without abstracting it into a rule + the way the rule-maker did. + +I've never been confronted to this before, and currently the OpenFisca DSL does +not provide any way of modelling this. Worst, as usually the law doesn't +contain this kind of abstractions, their use has always been discouraged as +_not being truthful to the text_ or _modelling as a developer and not as a +policy folk_. + +Strangely, in this particular case, things got a bit twisted. + ### 20.1.1 - [48](https://github.com/digitalaotearoa/openfisca-aotearoa/pull/48) * Test case. diff --git a/pyproject.toml b/pyproject.toml index 22b05544..42e7c083 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenFisca-Aotearoa" -version = "20.1.1" +version = "20.2.0" description = "OpenFisca rules-as-code system for Aotearoa." license = "AGPL-3.0-only" authors = [ From eb9a3bfefd60d809295e05ce08acd50a0cdffb86 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Sep 2023 05:04:00 +0200 Subject: [PATCH 05/12] Move ref to top --- .../acts/social_security/dictionary/abatement_rate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py index b56011af..21004646 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py @@ -1,3 +1,5 @@ +"""https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375""" + from numpy.typing import NDArray from typing import Callable @@ -21,8 +23,6 @@ class AbatementRate: "the rate at which a rate of benefit [...] must, under the appropriate income test, be reduced on account of income." - .. see:: https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375 - """ #: Applicable rate of benefit to be reduced. From ac2273f6e5d0333369ef82b8eac936f576adf0c3 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 20:54:51 +0200 Subject: [PATCH 06/12] Make income test a normal formula --- .../dictionary/income_test_1.py | 96 ++++++++++++++++ .../sole_parent_support_benefit.py | 108 ++++++++++++++---- .../exegesis/income_tested_benefit.py | 16 +++ 3 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py create mode 100644 openfisca_aotearoa/variables/exegesis/income_tested_benefit.py diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py new file mode 100644 index 00000000..8fce7b11 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py @@ -0,0 +1,96 @@ +"""This module provides the calculation of Income Test 1. + +Income Test 1 means that the applicable rate of [a] benefit must be +reduced by 30 cents for every $1 of the total income of the beneficiary and the +beneficiary’s spouse or partner that is more than $160 a week but not more than +$250 a week; and by 70 cents for every $1 of that income that is more than $250 +a week. + +""" + +# We import `inspect` to know where is this `variable` being called from. +import inspect + +# We import `os` to provide a useful error message when a benefit is not valid. +import os + +# We import numpy to use its `floor` function, needed for income tests as i.e. +# we need to reduce x¢ for every $y". +import numpy + +# We import the required OpenFisca modules needed to define a formula. +from openfisca_core import periods, variables + +# We import the required `entities` corresponding to our formulas. +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( + IncomeTestedBenefit, + ) + + +class social_security__income_test_1(variables.Variable): + label = "Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + # Where is this income-test being called from. + callers = [frame.filename for frame in inspect.stack()[5:7]] + + for benefit in IncomeTestedBenefit: + for caller in callers: + if benefit.name in caller: + applicable_rate = families.sum( + people(f"{benefit.name}__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner + total_income = families(f"{benefit.name}__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # that is more than $160 a week but not more than $250 + # a week; and + # (b) by 70 cents for every $1 of that income that is more + # than $250 a week + scale = ( + params(period) + .social_security + .dictionary + .income_test_1 + ) + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + raise InvalidBenefitError(callers) + + +class InvalidBenefitError(NotImplementedError): + """Raise when a benefit is not valid for an income test.""" + + def __init__(self, callers): + message = "The provided files do not contain an income-tested benefit:" + super().__init__(os.linesep.join([message, *callers])) diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index e775a270..93e16071 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -1,4 +1,4 @@ -"""This module provides eligibility and amount for Jobseeker Support.""" +"""This module provides eligibility and amount for Sole parent support.""" import numpy @@ -16,7 +16,6 @@ # For more information on OpenFisca's `entities`: # https://openfisca.org/doc/key-concepts/person,_entities,_role.html from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.acts.social_security import dictionary class sole_parent_support__benefit(variables.Variable): @@ -27,42 +26,105 @@ class sole_parent_support__benefit(variables.Variable): reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" set_input = holders.set_input_dispatch_by_period - def formula_2018_11_26(people, period, parameters): - return people("sole_parent_support__entitled", period) * numpy.clip(people("sole_parent_support__base", period) - people("sole_parent_support__reduction", period), 0, people("sole_parent_support__base", period)) + def formula_2018_11_26(people, period, _params): + entitled = people("sole_parent_support__entitled", period) + applicable_rate = people("sole_parent_support__base", period) + abatement_rate = people("sole_parent_support__abatement", period) + + return ( + + entitled + * numpy.clip(applicable_rate - abatement_rate, 0, applicable_rate) + ) class sole_parent_support__base(variables.Variable): - value_type = float + label = "Sole parent support — Applicable rate" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" + documentation = """ + Base amount or applicable rate of sole parent support (this is, taxed, + and the amounts are supplied after tax, i.e. net). + """ entity = entities.Person + value_type = float + default_value = 0 definition_period = periods.WEEK - reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" - label = "Jobseeker Support - Base Amount, (this is taxed and the amounts are supplied after tax, i.e. net)" - def formula_2018_11_26(people, period, parameters): + def formula_2018_11_26(people, period, params): + clause_1_weekly_benefit = ( + params(period.first_day) + .social_security + .sole_parent_support.base + ) + + return ( + + people("sole_parent_support__entitled", period) + * clause_1_weekly_benefit + ) - clause_1_weekly_benefit = parameters(period.first_day).social_security.sole_parent_support.base - return people("sole_parent_support__entitled", period) * clause_1_weekly_benefit +class sole_parent_support__abatement(variables.Variable): + label = "Sole parent support — Abatement rate" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" + documentation = """ + The amount the base benefit is reduced based on the appropriate Income + Test and the person & their partners income. + """ + entity = entities.Person + value_type = float + default_value = 0 + definition_period = periods.WEEK + def formula_2018_11_26(people, period, _params): + return people.family("social_security__income_test_1", period) -class sole_parent_support__reduction(variables.Variable): + +class sole_parent_support__income(variables.Variable): + label = "Sole parent support — Income" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" + documentation = """Total income of the people and their partners.""" + entity = entities.Family value_type = float - entity = entities.Person + default_value = 0 definition_period = periods.WEEK - label = "The amount the base benefit is reduced base on the appropriate Income Test and the person & their partners income" - reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" - def formula_2018_11_26(people, period, parameters): - applicable_rate = people("sole_parent_support__base", period) - family_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - childcare_deduction_limit = parameters(period.first_day).social_security.sole_parent_support.childcare_deduction_limit - total_income = family_income - numpy.clip(people("sole_parent_support__weekly_childcare_cost", period), 0, childcare_deduction_limit) - income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc - return ( - + people("sole_parent_support__entitled", period) - * dictionary.AbatementRate(applicable_rate, total_income)(income_test_1) + def formula_2018_11_26(families, period, params): + # 1. To a beneficiary with 1 or more dependent children + beneficiary_income = families.sum( + families.members("social_security__income", period), + role = entities.Family.PRINCIPAL, + ) + + spouse_or_partner_income = families.sum( + families.members("social_security__income", period), + role = entities.Family.PARTNER, ) + family_income = beneficiary_income + spouse_or_partner_income + + # 2. For the purposes of clause 1, MSD may disregard up to $20 a week + # of the beneficiary’s personal earnings used to meet the cost of + # childcare for any of the beneficiary’s dependent children. + childcare_deduction_limit = ( + params(period.first_day) + .social_security + .sole_parent_support + .childcare_deduction_limit + ) + + childcare_costs = families.sum( + families.members( + "sole_parent_support__weekly_childcare_cost", + period, + ) + ) + + total_income = ( + + family_income + - numpy.clip(childcare_costs, 0, childcare_deduction_limit) + ) + + return total_income + class sole_parent_support__weekly_childcare_cost(variables.Variable): value_type = float diff --git a/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py b/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py new file mode 100644 index 00000000..65b605f1 --- /dev/null +++ b/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py @@ -0,0 +1,16 @@ +"""Exegesis — Income-tested benefit. + +Income-tested benefit is a benefit subject to an income-test. While the term +has been repealed in favour of "main benefit", we use it to refer to any +benefit that is subject to an income-test, whether it is a main benefit or not. + +For example, the Accommodation Supplement is not a main benefit, but it is, +under certain conditions, subject to an income-test. + +""" + +from openfisca_core import indexed_enums + + +class IncomeTestedBenefit(indexed_enums.Enum): + sole_parent_support = "sole_parent_support" From 2c09c90300cccd59019ed25b16ec664a8309d053 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 21:36:50 +0200 Subject: [PATCH 07/12] Refllect changes in jobseeker support --- CONTRIBUTING.md | 6 +- .../dictionary/test_abatement_rate.py | 40 ------- .../jobseeker_support/base.yaml | 2 +- .../social_security/dictionary/__init__.py | 3 - .../dictionary/abatement_rate.py | 44 ------- .../dictionary/income_test_1.py | 3 +- .../dictionary/income_test_2.py | 95 +++++++++++++++ .../dictionary/income_test_3.py | 108 ++++++++++++++++++ .../dictionary/income_test_4.py | 88 ++++++++++++++ .../jobseeker_support__benefit.py | 67 +++++++---- .../sole_parent_support_benefit.py | 2 +- .../exegesis/income_tested_benefit.py | 3 + 12 files changed, 342 insertions(+), 119 deletions(-) delete mode 100644 openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py delete mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py delete mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 815139e0..b32c17ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -275,12 +275,10 @@ This project currently utilises one specific pattern however for benefit calcula - `jobseeker_support__entitled` (true/false) - or - `jobseeker_support__eligible` (true/false) - `jobseeker_support__base` (float) - `jobseeker_support__cutoff` (float) -- `jobseeker_support__reduction` (float) +- `jobseeker_support__abatement` (float) - `jobseeker_support__benefit` (float) i.e. the formula for `jobseeker_support__benefit` would be: ``` - jobseeker_support__entitled * min(jobseeker_support__base - jobseeker_support__reduction, jobseeker_support__cutoff) + jobseeker_support__entitled * min(jobseeker_support__base - jobseeker_support__abatement, jobseeker_support__cutoff) ``` - - diff --git a/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py b/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py deleted file mode 100644 index 7b469983..00000000 --- a/openfisca_aotearoa/tests/social_security/dictionary/test_abatement_rate.py +++ /dev/null @@ -1,40 +0,0 @@ -import numpy -import pytest - -from openfisca_core import taxscales, tools - -from openfisca_aotearoa.variables.acts.social_security import dictionary - - -class IncomeTest(taxscales.MarginalRateTaxScale): - ... - - -@pytest.fixture -def income_test() -> IncomeTest: - scale = IncomeTest() - scale.add_bracket(0, 0.00) - scale.add_bracket(160, 0.30) - scale.add_bracket(250, 0.70) - return scale - - -def test_abatement_rate_floor(income_test: IncomeTest) -> None: - rate = numpy.full((4,), 100.00) - income = numpy.array([159.99, 160.00, 160.99, 161.99]) - abatement = dictionary.AbatementRate(rate, income)(income_test.calc) - tools.assert_near(abatement, numpy.array([0.00, 0.00, 0.00, 0.30])) - - -def test_abatement_rate_ceiling(income_test: IncomeTest) -> None: - rate = numpy.full((4,), 100.00) - income = numpy.array([249.99, 250.00, 251.00, 251.99]) - abatement = dictionary.AbatementRate(rate, income)(income_test.calc) - tools.assert_near(abatement, numpy.array([26.70, 27.00, 27.70, 27.70])) - - -def test_abatement_rate_is_not_negative(income_test: IncomeTest) -> None: - rate = numpy.full((3,), 100.00) - income = numpy.array([354.00, 355.00, 356.00]) - abatement = dictionary.AbatementRate(rate, income)(income_test.calc) - tools.assert_near(abatement, numpy.array([99.80, 100.00, 100.00])) diff --git a/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml b/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml index 5154aad2..c63b49b2 100644 --- a/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml +++ b/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml @@ -216,7 +216,7 @@ 2022-W01: [240.65, 406.78, 239.70] 2023-W01: [283.00, 440.96, 274.37] 2023-W15: [303.43, 472.79, 294.18] - jobseeker_support__reduction: + jobseeker_support__abatement: 2020-W01: [143.5, 0, 0] 2021-W01: [59.5, 0, 0] 2021-W23: [0, 0, 0] diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py deleted file mode 100644 index 6eb6b78b..00000000 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .abatement_rate import AbatementRate - -__all__ = ["AbatementRate"] diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py deleted file mode 100644 index 21004646..00000000 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/abatement_rate.py +++ /dev/null @@ -1,44 +0,0 @@ -"""https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375""" - -from numpy.typing import NDArray -from typing import Callable - -import dataclasses - -import numpy - -Vector = NDArray[numpy.float_] - - -@dataclasses.dataclass(frozen=True) -class AbatementRate: - """Abatement rate applicable to income-tested benefits. - - Income test means that the applicable rate of a benefit must be reduced by - a certain amount for each unit of income defined by the policy-maker, above - a certain floor but below a certain ceiling. - - Abatement rate is the application of an income test to a specific benefit, - taking into account that benefit's own definition of total income; that is, - "the rate at which a rate of benefit [...] must, under the appropriate - income test, be reduced on account of income." - - """ - - #: Applicable rate of benefit to be reduced. - applicable_rate: Vector - - #: Total income of the beneficiary and the beneficiary’s spouse or partner. - total_income: Vector - - def __call__(self, income_test: Callable[[Vector], Vector]) -> Vector: - """Apply an income test to a benefit's applicable rate.""" - - # numpy.floor is required for income tests as i.e. "35c for every $1". - floor = numpy.floor(self.total_income) - - # The abatement rate regardless of benefit rate. - abatement_rate = income_test(floor) - - # The abatement rate capped at the applicable benefit rate. - return numpy.minimum(abatement_rate, self.applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py index 8fce7b11..586d9685 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py @@ -10,7 +10,6 @@ # We import `inspect` to know where is this `variable` being called from. import inspect - # We import `os` to provide a useful error message when a benefit is not valid. import os @@ -29,7 +28,7 @@ class social_security__income_test_1(variables.Variable): - label = "Income Test 1" + label = "Dictionary — Income Test 1" reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" documentation = """ Income Test 1 means that the applicable rate of [a] benefit must be diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py new file mode 100644 index 00000000..f9ffbedd --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py @@ -0,0 +1,95 @@ +"""This module provides the calculation of Income Test 2. + +Income Test 2 means that the applicable rate of [a] benefit must be reduced by +15 cents for every $1 of the total income of the beneficiary and the +beneficiary’s spouse or partner that is more than $160 a week but not more than +$250 a week; and by 35 cents for every $1 of that income that is more than +$250 a week. + +""" + +# We import `inspect` to know where is this `variable` being called from. +import inspect +# We import `os` to provide a useful error message when a benefit is not valid. +import os + +# We import numpy to use its `floor` function, needed for income tests as i.e. +# we need to reduce x¢ for every $y". +import numpy + +# We import the required OpenFisca modules needed to define a formula. +from openfisca_core import periods, variables + +# We import the required `entities` corresponding to our formulas. +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( + IncomeTestedBenefit, + ) + + +class social_security__income_test_2(variables.Variable): + label = "Dictionary — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + # Where is this income-test being called from. + callers = [frame.filename for frame in inspect.stack()[5:7]] + + for benefit in IncomeTestedBenefit: + for caller in callers: + if benefit.name in caller: + applicable_rate = families.sum( + people(f"{benefit.name}__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner + total_income = families(f"{benefit.name}__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # that is more than $160 a week but not more than $250 + # a week; and + # (b) by 35 cents for every $1 of that income that is more + # than $250 a week + scale = ( + params(period) + .social_security + .dictionary + .income_test_2 + ) + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + raise InvalidBenefitError(callers) + + +class InvalidBenefitError(NotImplementedError): + """Raise when a benefit is not valid for an income test.""" + + def __init__(self, callers): + message = "The provided files do not contain an income-tested benefit:" + super().__init__(os.linesep.join([message, *callers])) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py new file mode 100644 index 00000000..44b5cba6 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py @@ -0,0 +1,108 @@ +"""This module provides the calculation of Income Test 3. + +Income Test 3 means that the applicable rate of [a] benefit must be reduced by +70 cents for every $1 of total income of the beneficiary and the beneficiary’s +spouse or partner that is more than, if the rate of benefit is a rate of New +Zealand superannuation stated in clause 1 of Part 2 of Schedule 1 of the New +Zealand Superannuation and Retirement Income Act 2001, $160 a week; or +in any other case, $160 a week. + +""" + +# We import `inspect` to know where is this `variable` being called from. +import inspect +# We import `os` to provide a useful error message when a benefit is not valid. +import os + +# We import numpy to use its `floor` function, needed for income tests as i.e. +# we need to reduce x¢ for every $y". +import numpy + +# We import the required OpenFisca modules needed to define a formula. +from openfisca_core import periods, variables + +# We import the required `entities` corresponding to our formulas. +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( + IncomeTestedBenefit, + ) + + +class social_security__income_test_3(variables.Variable): + label = "Dictionary — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + # Where is this income-test being called from. + callers = [frame.filename for frame in inspect.stack()[5:7]] + + for benefit in IncomeTestedBenefit: + for caller in callers: + if benefit.name in caller: + applicable_rate = families.sum( + people(f"{benefit.name}__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families(f"{benefit.name}__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + if benefit.name in ("superannuation", "veterans_pension"): + scale = ( + params(period) + .social_security + .dictionary + .income_test_3a + ) + + # (b) in any other case, $160 a week + else: + scale = ( + params(period) + .social_security + .dictionary + .income_test_3b + ) + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + raise InvalidBenefitError(callers) + + +class InvalidBenefitError(NotImplementedError): + """Raise when a benefit is not valid for an income test.""" + + def __init__(self, callers): + message = "The provided files do not contain an income-tested benefit:" + super().__init__(os.linesep.join([message, *callers])) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py new file mode 100644 index 00000000..f41316a7 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py @@ -0,0 +1,88 @@ +"""This module provides the calculation of Income Test 4. + +Income Test 4 means that the applicable rate of [a] benefit must be reduced by +35 cents for every $1 of the total income of the beneficiary and the +beneficiary’s spouse or partner that is more than $160 a week. + +""" + +# We import `inspect` to know where is this `variable` being called from. +import inspect +# We import `os` to provide a useful error message when a benefit is not valid. +import os + +# We import numpy to use its `floor` function, needed for income tests as i.e. +# we need to reduce x¢ for every $y". +import numpy + +# We import the required OpenFisca modules needed to define a formula. +from openfisca_core import periods, variables + +# We import the required `entities` corresponding to our formulas. +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( + IncomeTestedBenefit, + ) + + +class social_security__income_test_4(variables.Variable): + label = "Dictionary — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + # Where is this income-test being called from. + callers = [frame.filename for frame in inspect.stack()[5:7]] + + for benefit in IncomeTestedBenefit: + for caller in callers: + if benefit.name in caller: + applicable_rate = families.sum( + people(f"{benefit.name}__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families(f"{benefit.name}__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = ( + params(period) + .social_security + .dictionary + .income_test_4 + ) + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + raise InvalidBenefitError(callers) + + +class InvalidBenefitError(NotImplementedError): + """Raise when a benefit is not valid for an income test.""" + + def __init__(self, callers): + message = "The provided files do not contain an income-tested benefit:" + super().__init__(os.linesep.join([message, *callers])) diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py index b59d2643..5ce9bab9 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py @@ -27,17 +27,19 @@ class jobseeker_support__benefit(variables.Variable): reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" def formula_2018_11_26(people, period, parameters): - return people("jobseeker_support__entitled", period) * numpy.clip(people("jobseeker_support__base", period) - people("jobseeker_support__reduction", period), 0, people("jobseeker_support__base", period)) + return people("jobseeker_support__entitled", period) * numpy.clip(people("jobseeker_support__base", period) - people("jobseeker_support__abatement", period), 0, people("jobseeker_support__base", period)) -class jobseeker_support__reduction(variables.Variable): +class jobseeker_support__abatement(variables.Variable): value_type = float entity = entities.Person definition_period = periods.WEEK label = "The amount the base benefit is reduced base on the appropriate Income Test and the person & their partners income" reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" - def formula_2018_11_26(people, period, parameters): + def formula_2018_11_26(people, period, _params): + families = people.family + case_1 = ( + people("schedule_4__part1_1_c", period) + people("schedule_4__part1_1_e", period) @@ -57,24 +59,22 @@ def formula_2018_11_26(people, period, parameters): + people("schedule_4__part1_1_h", period) ) - applicable_rate = people("jobseeker_support__base", period) - total_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - abatement = dictionary.AbatementRate(applicable_rate, total_income) - - income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc - income_test_3 = parameters(period).social_security.dictionary.income_test_3b.calc - income_test_4 = parameters(period).social_security.dictionary.income_test_4.calc + income_test_1 = families("social_security__income_test_1", period) + income_test_3 = families("social_security__income_test_3", period) + income_test_4 = families("social_security__income_test_4", period) return ( + people("jobseeker_support__entitled", period) * ( - + case_1 * abatement(income_test_1) - + case_3 * abatement(income_test_3) - + case_4 * abatement(income_test_4) + + case_1 * income_test_1 + + case_3 * income_test_3 + + case_4 * income_test_4 ) ) - def formula_2020_11_09(people, period, parameters): + def formula_2020_11_09(people, period, _params): + families = people.family + case_1 = ( + people("schedule_4__part1_1_c", period) + people("schedule_4__part1_1_e", period) @@ -93,24 +93,43 @@ def formula_2020_11_09(people, period, parameters): + people("schedule_4__part1_1_h", period) ) - applicable_rate = people("jobseeker_support__base", period) - total_income = people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PARTNER) + people.family.sum(people.family.members("social_security__income", period), role=entities.Family.PRINCIPAL) - abatement = dictionary.AbatementRate(applicable_rate, total_income) - - income_test_1 = parameters(period).social_security.dictionary.income_test_1.calc - income_test_3 = parameters(period).social_security.dictionary.income_test_3b.calc - income_test_4 = parameters(period).social_security.dictionary.income_test_4.calc + income_test_1 = families("social_security__income_test_1", period) + income_test_3 = families("social_security__income_test_3", period) + income_test_4 = families("social_security__income_test_4", period) return ( + people("jobseeker_support__entitled", period) * ( - + case_1 * abatement(income_test_1) - + case_3 * abatement(income_test_3) - + case_4 * abatement(income_test_4) + + case_1 * income_test_1 + + case_3 * income_test_3 + + case_4 * income_test_4 ) ) +class jobseeker_support__income(variables.Variable): + label = "Jobseeker support — Income" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" + documentation = """Total income of the people and their partners.""" + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.WEEK + + def formula_2018_11_26(families, period, _params): + beneficiary_income = families.sum( + families.members("social_security__income", period), + role = entities.Family.PRINCIPAL, + ) + + spouse_or_partner_income = families.sum( + families.members("social_security__income", period), + role = entities.Family.PARTNER, + ) + + return beneficiary_income + spouse_or_partner_income + + class jobseeker_support__base(variables.Variable): value_type = float entity = entities.Person diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index 93e16071..b017f27a 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -1,4 +1,4 @@ -"""This module provides eligibility and amount for Sole parent support.""" +"""This module provides eligibility and amount for Sole Parent Support.""" import numpy diff --git a/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py b/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py index 65b605f1..75f75eb4 100644 --- a/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py +++ b/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py @@ -13,4 +13,7 @@ class IncomeTestedBenefit(indexed_enums.Enum): + jobseeker_support = "jobseeker_support" sole_parent_support = "sole_parent_support" + superannuation = "superannuation" + veterans_pension = "veterans_pension" From 6ef2edff9f0afbff5cc3210b61ad0212a58bfafb Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 21:38:07 +0200 Subject: [PATCH 08/12] Fix linter --- .../jobseeker_support/jobseeker_support__benefit.py | 1 - .../sole_parent_support/sole_parent_support_benefit.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py index 5ce9bab9..79677a24 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py @@ -16,7 +16,6 @@ # For more information on OpenFisca's `entities`: # https://openfisca.org/doc/key-concepts/person,_entities,_role.html from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.acts.social_security import dictionary class jobseeker_support__benefit(variables.Variable): diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index b017f27a..d1750554 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -115,7 +115,7 @@ def formula_2018_11_26(families, period, params): families.members( "sole_parent_support__weekly_childcare_cost", period, - ) + ), ) total_income = ( From 3a10e521c6b33bd27bfdc64291c61564ac0ecef2 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 21:46:17 +0200 Subject: [PATCH 09/12] Fix caps --- .../sole_parent_support/sole_parent_support_benefit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index d1750554..78c2f4ae 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -38,7 +38,7 @@ def formula_2018_11_26(people, period, _params): class sole_parent_support__base(variables.Variable): - label = "Sole parent support — Applicable rate" + label = "Sole Parent Support — Applicable rate" reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" documentation = """ Base amount or applicable rate of sole parent support (this is, taxed, @@ -63,7 +63,7 @@ def formula_2018_11_26(people, period, params): class sole_parent_support__abatement(variables.Variable): - label = "Sole parent support — Abatement rate" + label = "Sole Parent Support — Abatement rate" reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" documentation = """ The amount the base benefit is reduced based on the appropriate Income @@ -79,7 +79,7 @@ def formula_2018_11_26(people, period, _params): class sole_parent_support__income(variables.Variable): - label = "Sole parent support — Income" + label = "Sole Parent Support — Income" reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784854.html" documentation = """Total income of the people and their partners.""" entity = entities.Family From 6225097ce4fb30886a7b936165717aebaa1597fd Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 21:57:02 +0200 Subject: [PATCH 10/12] Make it a breaking change --- CHANGELOG.md | 37 ++++++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a166a5fe..633004ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # Changelog -## 20.2.0 - [59](https://github.com/digitalaotearoa/openfisca-aotearoa/pull/59) +## 21.0.0 - [59](https://github.com/digitalaotearoa/openfisca-aotearoa/pull/59) * Tax and benefit system evolution. * Impacted periods: all. -* Impacted areas: `sole_parewnt_support`, `jobseeker_support` +* Impacted areas: `sole_parent_support`, `jobseeker_support` * Details: - - Add `abatement_rate` as the result of applying an income test to a benefit + - Add `income_test_n` as the result of applying an income test to a benefit. #### Discussion @@ -14,32 +14,39 @@ Main benefits are income-tested (subject to an abatement rate based on income). Non-main benefits can be income-tested via main benefits (i.e. accommodation supplement via jobseeker). However, some benefits change the definitions of certain things, i.e. income is not the same for accommodation supplement and -for jobseeker support. +for jobseeker support. Furthermore, the notion of income-tested benefit has +been replaced with that of main benefit, which can create confusion when a +benefit like accommodation supplement is income-tested via jobseeker support. This poses a challenge to the rule-maker, because in order to define a general rule for an income test, that applies to benefits with different definitions of income, it has to make it _high order_. In plain English, it has to create a -_rule factory_. Something that creates a rule, that just then can then be -applied. +_rule factory_. A rule that creates a rule, depending on the context of its +application (i.e. the benefit it is applied to). There are several ways to solve this problem, yet all gravitate around two philosophies: 1. Stick to the letter: if the rule-maker defines a _pure high-order function_ - that can't be applied directly, just model it as close to the letter that is - possible. + that can't be applied directly, one solution is to just model it as close to + the letter that is possible. 2. Incorporate: if the rule can't be applied directly, ignore it, and incorporate it as many times as is used without abstracting it into a rule - the way the rule-maker did. + the way the rule-maker did (cf. copy-paste several times). -I've never been confronted to this before, and currently the OpenFisca DSL does -not provide any way of modelling this. Worst, as usually the law doesn't -contain this kind of abstractions, their use has always been discouraged as -_not being truthful to the text_ or _modelling as a developer and not as a -policy folk_. +Currently the OpenFisca DSL does not provide any native way of modelling this +cases, as it is assumed as an invariant that, what can be modelled, can be +directly applied at definition time (just following the text), and not at +run time (depending on the context it is being applied). -Strangely, in this particular case, things got a bit twisted. +As usually the law doesn't contain this kind of abstractions, their use has +always been discouraged as _not being truthful to the text_ or _modelling as a +developer and not as a policy folk_. This case is, then, peculiar. + +The solution adopted here is to model the income test as a normal `Variable`, +and capturing the context of its application from the stack. While it works, +it is not a very elegant solution, and it is likely error-prone. ### 20.1.1 - [48](https://github.com/digitalaotearoa/openfisca-aotearoa/pull/48) diff --git a/pyproject.toml b/pyproject.toml index 42e7c083..92adb3a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenFisca-Aotearoa" -version = "20.2.0" +version = "21.0.0" description = "OpenFisca rules-as-code system for Aotearoa." license = "AGPL-3.0-only" authors = [ From e76a40ee5b1dcec51a03323adbb0b083f7784050 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 1 Oct 2023 23:25:30 +0200 Subject: [PATCH 11/12] Consolidate jobseeker support --- .../jobseeker_support/abatement.yaml | 59 +++++++++++ .../jobseeker_support/base.yaml | 7 -- .../jobseeker_support/income.yaml | 53 ++++++++++ .../jobseeker_support/abatement.py | 86 +++++++++++++++ .../jobseeker_support/income.py | 35 ++++++ .../jobseeker_support__benefit.py | 100 ------------------ 6 files changed, 233 insertions(+), 107 deletions(-) create mode 100644 openfisca_aotearoa/tests/social_security/jobseeker_support/abatement.yaml create mode 100644 openfisca_aotearoa/tests/social_security/jobseeker_support/income.yaml create mode 100644 openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income.py diff --git a/openfisca_aotearoa/tests/social_security/jobseeker_support/abatement.yaml b/openfisca_aotearoa/tests/social_security/jobseeker_support/abatement.yaml new file mode 100644 index 00000000..cba7bfb4 --- /dev/null +++ b/openfisca_aotearoa/tests/social_security/jobseeker_support/abatement.yaml @@ -0,0 +1,59 @@ +- name: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 1 - + The applicable rate of benefit must be reduced— + (a) by 30 cents for every $1 of the total income of the beneficiary and + the beneficiary’s spouse or partner that is more than $160 a week but + not more than $250 a week + (b) by 70 cents for every $1 of that income that is more than $250 a week + period: 2018-11-26 + input: + jobseeker_support__income: + 2023-W01: [159.99, 160.00, 160.01, 161.99, 249.99, 250.00, 250.01, 251.99] + schedule_4__part1_1_c: + 2023-W01: [true, true, true, true, true, true, true, true] + output: + jobseeker_support__abatement: + 2023-W01: [0, 0, 0, 0.30, 26.70, 27.00, 27.00, 27.70] + + +- name: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 3 - + The applicable rate of benefit must be reduced by 70 cents for every $1 of + the total income of the beneficiary and the beneficiary’s spouse or partner + that is more than,— + (b) [...] $160 a week + period: 2018-11-26 + input: + jobseeker_support__income: + 2023-W01: [159.99, 160.00, 160.01, 161.99, 249.99, 250.00, 250.01, 251.99] + schedule_4__part1_1_a: + 2023-W01: [true, true, true, true, true, true, true, true] + output: + jobseeker_support__abatement: + 2023-W01: [0, 0, 0, 0.70, 62.30, 63.00, 63.00, 63.70] + + +- name: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 4 - + The applicable rate of benefit must be reduced by 35 cents for every $1 of + the total income of the beneficiary and the beneficiary’s spouse or partner + that is more than $160 a week + period: 2018-11-26 + input: + jobseeker_support__income: + 2023-W01: [159.99, 160.00, 160.01, 161.99, 249.99, 250.00, 250.01, 251.99] + schedule_4__part1_1_d: + 2023-W01: [false, false, false, false, false, false, false, false] + schedule_4__part1_1_g: + 2023-W01: [true, true, true, true, true, true, true, true] + output: + jobseeker_support__abatement: + 2023-W01: [0, 0, 0, 0.35, 31.15, 31.50, 31.50, 31.85] + schedule_4__part1_1_d: + 2023-W01: [false, false, false, false, false, false, false, false] diff --git a/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml b/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml index c63b49b2..8e44cc5d 100644 --- a/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml +++ b/openfisca_aotearoa/tests/social_security/jobseeker_support/base.yaml @@ -216,13 +216,6 @@ 2022-W01: [240.65, 406.78, 239.70] 2023-W01: [283.00, 440.96, 274.37] 2023-W15: [303.43, 472.79, 294.18] - jobseeker_support__abatement: - 2020-W01: [143.5, 0, 0] - 2021-W01: [59.5, 0, 0] - 2021-W23: [0, 0, 0] - 2022-W01: [138.6, 0, 0] - 2023-W01: [31.5, 0, 0] - 2023-W15: [31.5, 0, 0] jobseeker_support__benefit: 2020-W01: [52, 0, 0] 2021-W01: [154.53, 0, 0] diff --git a/openfisca_aotearoa/tests/social_security/jobseeker_support/income.yaml b/openfisca_aotearoa/tests/social_security/jobseeker_support/income.yaml new file mode 100644 index 00000000..fd646740 --- /dev/null +++ b/openfisca_aotearoa/tests/social_security/jobseeker_support/income.yaml @@ -0,0 +1,53 @@ +- name: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 1-4 - + The applicable rate of benefit must be reduced [...] of the total income of + the beneficiary and the beneficiary’s spouse or partner. + period: 2018-11-26 + input: + persons: + Vrenda: + social_security__income: + 2023-W01: 0 + 2023-W02: 100 + 2023-W03: 100 + Piedad: + social_security__income: + 2023-W01: 0 + 2023-W02: 0 + 2023-W03: 100 + output: + jobseeker_support__income: + 2023-W01: [0, 0] + 2023-W02: [100, 0] + 2023-W03: [100, 100] + + +- name: > + Social Security Act 2018 - + Schedule 2 Dictionary - + Income test 1-4 - + The applicable rate of benefit must be reduced [...] of the total income of + the beneficiary and the beneficiary’s spouse or partner. + period: 2018-11-26 + input: + persons: + Vrenda: + social_security__income: + 2023-W01: 0 + 2023-W02: 100 + 2023-W03: 100 + Piedad: + social_security__income: + 2023-W01: 0 + 2023-W02: 0 + 2023-W03: 100 + family: + principal: Vrenda + partners: [Piedad] + output: + jobseeker_support__income: + 2023-W01: [0, 0] + 2023-W02: [100, 100] + 2023-W03: [200, 200] diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py new file mode 100644 index 00000000..e40ce3b6 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py @@ -0,0 +1,86 @@ +"""Jobseeker Support — Abatement. + +The amount the base benefit is reduced based on the appropriate Income Test and +the person & their partners income. + +""" + +from openfisca_core import periods, variables + +from openfisca_aotearoa import entities + + +class jobseeker_support__abatement(variables.Variable): + label = "Jobseeker Support — Abatement" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" + documentation = """ + The amount the base benefit is reduced based on the appropriate Income + Test and the person & their partners income. + """ + entity = entities.Person + value_type = float + default_value = 0 + definition_period = periods.WEEK + + def formula_2018_11_26(people, period, _params): + families = people.family + + case_1 = ( + + people("schedule_4__part1_1_c", period) + + people("schedule_4__part1_1_e", period) + + people("schedule_4__part1_1_f", period) + ) + + case_3 = ( + + people("schedule_4__part1_1_a", period) + + people("schedule_4__part1_1_b", period) + + people("schedule_4__part1_1_d", period) + + people("schedule_4__part1_1_i", period) + + people("schedule_4__part1_1_j", period) + ) + + case_4 = ( + + people("schedule_4__part1_1_g", period) + + people("schedule_4__part1_1_h", period) + ) + + income_test_1 = families("social_security__income_test_1", period) + income_test_3 = families("social_security__income_test_3", period) + income_test_4 = families("social_security__income_test_4", period) + + return ( + + case_1 * income_test_1 + + case_3 * income_test_3 + + case_4 * income_test_4 + ) + + def formula_2020_11_09(people, period, _params): + families = people.family + + case_1 = ( + + people("schedule_4__part1_1_c", period) + + people("schedule_4__part1_1_e", period) + + people("schedule_4__part1_1_f", period) + ) + + case_3 = ( + + people("schedule_4__part1_1_a", period) + + people("schedule_4__part1_1_b", period) + + people("schedule_4__part1_1_d", period) + + people("schedule_4__part1_1_j", period) + ) + + case_4 = ( + + people("schedule_4__part1_1_g", period) + + people("schedule_4__part1_1_h", period) + ) + + income_test_1 = families("social_security__income_test_1", period) + income_test_3 = families("social_security__income_test_3", period) + income_test_4 = families("social_security__income_test_4", period) + + return ( + + case_1 * income_test_1 + + case_3 * income_test_3 + + case_4 * income_test_4 + ) diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income.py new file mode 100644 index 00000000..97b6678e --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income.py @@ -0,0 +1,35 @@ +"""Jobseeker Support — Income. + +The income upon which the applicable rate of Jobseeker Support is calculated +and eventually reduced. + +""" + +from openfisca_core import periods, variables + +from openfisca_aotearoa import entities + + +class jobseeker_support__income(variables.Variable): + label = "Jobseeker support — Income" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" + documentation = """Total income of people and their partners.""" + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.WEEK + + def formula_2018_11_26(families, period, _params): + people = families.members + + beneficiary_income = families.sum( + people("social_security__income", period), + role = entities.Family.PRINCIPAL, + ) + + spouse_or_partner_income = families.sum( + people("social_security__income", period), + role = entities.Family.PARTNER, + ) + + return beneficiary_income + spouse_or_partner_income diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py index 79677a24..183d68f9 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/jobseeker_support__benefit.py @@ -29,106 +29,6 @@ def formula_2018_11_26(people, period, parameters): return people("jobseeker_support__entitled", period) * numpy.clip(people("jobseeker_support__base", period) - people("jobseeker_support__abatement", period), 0, people("jobseeker_support__base", period)) -class jobseeker_support__abatement(variables.Variable): - value_type = float - entity = entities.Person - definition_period = periods.WEEK - label = "The amount the base benefit is reduced base on the appropriate Income Test and the person & their partners income" - reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" - - def formula_2018_11_26(people, period, _params): - families = people.family - - case_1 = ( - + people("schedule_4__part1_1_c", period) - + people("schedule_4__part1_1_e", period) - + people("schedule_4__part1_1_f", period) - ) - - case_3 = ( - + people("schedule_4__part1_1_a", period) - + people("schedule_4__part1_1_b", period) - + people("schedule_4__part1_1_d", period) - + people("schedule_4__part1_1_i", period) - + people("schedule_4__part1_1_j", period) - ) - - case_4 = ( - + people("schedule_4__part1_1_g", period) - + people("schedule_4__part1_1_h", period) - ) - - income_test_1 = families("social_security__income_test_1", period) - income_test_3 = families("social_security__income_test_3", period) - income_test_4 = families("social_security__income_test_4", period) - - return ( - + people("jobseeker_support__entitled", period) - * ( - + case_1 * income_test_1 - + case_3 * income_test_3 - + case_4 * income_test_4 - ) - ) - - def formula_2020_11_09(people, period, _params): - families = people.family - - case_1 = ( - + people("schedule_4__part1_1_c", period) - + people("schedule_4__part1_1_e", period) - + people("schedule_4__part1_1_f", period) - ) - - case_3 = ( - + people("schedule_4__part1_1_a", period) - + people("schedule_4__part1_1_b", period) - + people("schedule_4__part1_1_d", period) - + people("schedule_4__part1_1_j", period) - ) - - case_4 = ( - + people("schedule_4__part1_1_g", period) - + people("schedule_4__part1_1_h", period) - ) - - income_test_1 = families("social_security__income_test_1", period) - income_test_3 = families("social_security__income_test_3", period) - income_test_4 = families("social_security__income_test_4", period) - - return ( - + people("jobseeker_support__entitled", period) - * ( - + case_1 * income_test_1 - + case_3 * income_test_3 - + case_4 * income_test_4 - ) - ) - - -class jobseeker_support__income(variables.Variable): - label = "Jobseeker support — Income" - reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/DLM6784850.html" - documentation = """Total income of the people and their partners.""" - entity = entities.Family - value_type = float - default_value = 0 - definition_period = periods.WEEK - - def formula_2018_11_26(families, period, _params): - beneficiary_income = families.sum( - families.members("social_security__income", period), - role = entities.Family.PRINCIPAL, - ) - - spouse_or_partner_income = families.sum( - families.members("social_security__income", period), - role = entities.Family.PARTNER, - ) - - return beneficiary_income + spouse_or_partner_income - - class jobseeker_support__base(variables.Variable): value_type = float entity = entities.Person From 54866d28a2671297de824348cb610c2574120ddd Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 7 Oct 2023 23:20:29 +0200 Subject: [PATCH 12/12] Add missing income tests --- .../accommodation_supplement/income_tests.py | 192 ++++++++++++++++++ .../social_security/dictionary/__init__.py | 13 ++ .../dictionary/income_test_1.py | 66 +----- .../dictionary/income_test_2.py | 64 +----- .../dictionary/income_test_3.py | 75 +------ .../dictionary/income_test_4.py | 61 +----- .../emergency_benefit/income_tests.py | 192 ++++++++++++++++++ .../jobseeker_support/abatement.py | 12 +- .../jobseeker_support/income_tests.py | 192 ++++++++++++++++++ .../sole_parent_support/income_tests.py | 192 ++++++++++++++++++ .../sole_parent_support_benefit.py | 2 +- .../superannuation/income_tests.py | 192 ++++++++++++++++++ .../supported_living_payment/income_tests.py | 192 ++++++++++++++++++ .../veterans_pension/income_tests.py | 192 ++++++++++++++++++ .../young_parent_payment/income_tests.py | 192 ++++++++++++++++++ .../youth_payment/income_tests.py | 192 ++++++++++++++++++ .../exegesis/income_tested_benefit.py | 19 -- 17 files changed, 1761 insertions(+), 279 deletions(-) create mode 100644 openfisca_aotearoa/variables/acts/social_security/accommodation_supplement/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/emergency_benefit/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/sole_parent_support/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/superannuation/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/supported_living_payment/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/veterans_pension/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/young_parent_payment/income_tests.py create mode 100644 openfisca_aotearoa/variables/acts/social_security/youth_payment/income_tests.py delete mode 100644 openfisca_aotearoa/variables/exegesis/income_tested_benefit.py diff --git a/openfisca_aotearoa/variables/acts/social_security/accommodation_supplement/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/accommodation_supplement/income_tests.py new file mode 100644 index 00000000..35535aaf --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/accommodation_supplement/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Accommodation Supplement's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class accommodation_supplement__income_test_1(dictionary.social_security__income_test_1): + label = "Accommodation Supplement — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("accommodation_supplement__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("accommodation_supplement__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class accommodation_supplement__income_test_2(dictionary.social_security__income_test_1): + label = "Accommodation Supplement — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("accommodation_supplement__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("accommodation_supplement__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class accommodation_supplement__income_test_3(dictionary.social_security__income_test_3): + label = "Accommodation Supplement — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("accommodation_supplement__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("accommodation_supplement__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class accommodation_supplement__income_test_4(dictionary.social_security__income_test_4): + label = "Accommodation Supplement — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("accommodation_supplement__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("accommodation_supplement__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py new file mode 100644 index 00000000..44c2b29c --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/__init__.py @@ -0,0 +1,13 @@ +"""Social Security Act's Dictionary.""" + +from .income_test_1 import social_security__income_test_1 +from .income_test_2 import social_security__income_test_2 +from .income_test_3 import social_security__income_test_3 +from .income_test_4 import social_security__income_test_4 + +__all__ = ( + "social_security__income_test_1", + "social_security__income_test_2", + "social_security__income_test_3", + "social_security__income_test_4", + ) diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py index 586d9685..c9e4adec 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_1.py @@ -1,4 +1,4 @@ -"""This module provides the calculation of Income Test 1. +"""This module provides the base Variable for calculation of Income Test 1. Income Test 1 means that the applicable rate of [a] benefit must be reduced by 30 cents for every $1 of the total income of the beneficiary and the @@ -8,23 +8,13 @@ """ -# We import `inspect` to know where is this `variable` being called from. -import inspect -# We import `os` to provide a useful error message when a benefit is not valid. -import os - -# We import numpy to use its `floor` function, needed for income tests as i.e. -# we need to reduce x¢ for every $y". -import numpy +from abc import abstractmethod # We import the required OpenFisca modules needed to define a formula. from openfisca_core import periods, variables # We import the required `entities` corresponding to our formulas. from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( - IncomeTestedBenefit, - ) class social_security__income_test_1(variables.Variable): @@ -42,54 +32,6 @@ class social_security__income_test_1(variables.Variable): default_value = 0 definition_period = periods.DateUnit.WEEK + @abstractmethod def formula_2018_11_26(families, period, params): - # Income Test 1 means that the applicable rate of [a] benefit must be - # reduced— - - # List of people with families. - people = families.members - - # Where is this income-test being called from. - callers = [frame.filename for frame in inspect.stack()[5:7]] - - for benefit in IncomeTestedBenefit: - for caller in callers: - if benefit.name in caller: - applicable_rate = families.sum( - people(f"{benefit.name}__base", period), - role = entities.Family.PRINCIPAL, - ) - - # (a) by 30 cents for every $1 of the total income of the - # beneficiary and the beneficiary’s spouse or partner - total_income = families(f"{benefit.name}__income", period) - - # Required for income tests as i.e. x¢ for every $y. - floor = numpy.floor(total_income) - - # that is more than $160 a week but not more than $250 - # a week; and - # (b) by 70 cents for every $1 of that income that is more - # than $250 a week - scale = ( - params(period) - .social_security - .dictionary - .income_test_1 - ) - - # The abatement rate regardless of benefit rate. - abatement_rate = scale.calc(floor) - - # The abatement rate capped at the applicable benefit rate. - return numpy.minimum(abatement_rate, applicable_rate) - - raise InvalidBenefitError(callers) - - -class InvalidBenefitError(NotImplementedError): - """Raise when a benefit is not valid for an income test.""" - - def __init__(self, callers): - message = "The provided files do not contain an income-tested benefit:" - super().__init__(os.linesep.join([message, *callers])) + raise NotImplementedError() diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py index f9ffbedd..5e7cddc7 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_2.py @@ -8,23 +8,13 @@ """ -# We import `inspect` to know where is this `variable` being called from. -import inspect -# We import `os` to provide a useful error message when a benefit is not valid. -import os - -# We import numpy to use its `floor` function, needed for income tests as i.e. -# we need to reduce x¢ for every $y". -import numpy +from abc import abstractmethod # We import the required OpenFisca modules needed to define a formula. from openfisca_core import periods, variables # We import the required `entities` corresponding to our formulas. from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( - IncomeTestedBenefit, - ) class social_security__income_test_2(variables.Variable): @@ -42,54 +32,6 @@ class social_security__income_test_2(variables.Variable): default_value = 0 definition_period = periods.DateUnit.WEEK + @abstractmethod def formula_2018_11_26(families, period, params): - # Income Test 2 means that the applicable rate of benefit must be - # reduced— - - # List of people with families. - people = families.members - - # Where is this income-test being called from. - callers = [frame.filename for frame in inspect.stack()[5:7]] - - for benefit in IncomeTestedBenefit: - for caller in callers: - if benefit.name in caller: - applicable_rate = families.sum( - people(f"{benefit.name}__base", period), - role = entities.Family.PRINCIPAL, - ) - - # (a) by 15 cents for every $1 of the total income of the - # beneficiary and the beneficiary’s spouse or partner - total_income = families(f"{benefit.name}__income", period) - - # Required for income tests as i.e. x¢ for every $y. - floor = numpy.floor(total_income) - - # that is more than $160 a week but not more than $250 - # a week; and - # (b) by 35 cents for every $1 of that income that is more - # than $250 a week - scale = ( - params(period) - .social_security - .dictionary - .income_test_2 - ) - - # The abatement rate regardless of benefit rate. - abatement_rate = scale.calc(floor) - - # The abatement rate capped at the applicable benefit rate. - return numpy.minimum(abatement_rate, applicable_rate) - - raise InvalidBenefitError(callers) - - -class InvalidBenefitError(NotImplementedError): - """Raise when a benefit is not valid for an income test.""" - - def __init__(self, callers): - message = "The provided files do not contain an income-tested benefit:" - super().__init__(os.linesep.join([message, *callers])) + raise NotImplementedError() diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py index 44b5cba6..6481c465 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_3.py @@ -9,23 +9,13 @@ """ -# We import `inspect` to know where is this `variable` being called from. -import inspect -# We import `os` to provide a useful error message when a benefit is not valid. -import os - -# We import numpy to use its `floor` function, needed for income tests as i.e. -# we need to reduce x¢ for every $y". -import numpy +from abc import abstractmethod # We import the required OpenFisca modules needed to define a formula. from openfisca_core import periods, variables # We import the required `entities` corresponding to our formulas. from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( - IncomeTestedBenefit, - ) class social_security__income_test_3(variables.Variable): @@ -44,65 +34,6 @@ class social_security__income_test_3(variables.Variable): default_value = 0 definition_period = periods.DateUnit.WEEK + @abstractmethod def formula_2018_11_26(families, period, params): - # Income Test 3 means that the applicable rate of benefit must be - # reduced— - - # List of people with families. - people = families.members - - # Where is this income-test being called from. - callers = [frame.filename for frame in inspect.stack()[5:7]] - - for benefit in IncomeTestedBenefit: - for caller in callers: - if benefit.name in caller: - applicable_rate = families.sum( - people(f"{benefit.name}__base", period), - role = entities.Family.PRINCIPAL, - ) - - # by 70 cents for every $1 of total income of the - # beneficiary and the beneficiary’s spouse or partner that - # is more than,— - total_income = families(f"{benefit.name}__income", period) - - # Required for income tests as i.e. x¢ for every $y. - floor = numpy.floor(total_income) - - # (a) if the rate of benefit is a rate of New Zealand - # superannuation stated in clause 1 of Part 2 of - # Schedule 1 of the New Zealand Superannuation and - # Retirement Income Act 2001, $160 a week; or - if benefit.name in ("superannuation", "veterans_pension"): - scale = ( - params(period) - .social_security - .dictionary - .income_test_3a - ) - - # (b) in any other case, $160 a week - else: - scale = ( - params(period) - .social_security - .dictionary - .income_test_3b - ) - - # The abatement rate regardless of benefit rate. - abatement_rate = scale.calc(floor) - - # The abatement rate capped at the applicable benefit rate. - return numpy.minimum(abatement_rate, applicable_rate) - - raise InvalidBenefitError(callers) - - -class InvalidBenefitError(NotImplementedError): - """Raise when a benefit is not valid for an income test.""" - - def __init__(self, callers): - message = "The provided files do not contain an income-tested benefit:" - super().__init__(os.linesep.join([message, *callers])) + raise NotImplementedError() diff --git a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py index f41316a7..a71e8dc5 100644 --- a/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py +++ b/openfisca_aotearoa/variables/acts/social_security/dictionary/income_test_4.py @@ -6,23 +6,13 @@ """ -# We import `inspect` to know where is this `variable` being called from. -import inspect -# We import `os` to provide a useful error message when a benefit is not valid. -import os - -# We import numpy to use its `floor` function, needed for income tests as i.e. -# we need to reduce x¢ for every $y". -import numpy +from abc import abstractmethod # We import the required OpenFisca modules needed to define a formula. from openfisca_core import periods, variables # We import the required `entities` corresponding to our formulas. from openfisca_aotearoa import entities -from openfisca_aotearoa.variables.exegesis.income_tested_benefit import ( - IncomeTestedBenefit, - ) class social_security__income_test_4(variables.Variable): @@ -38,51 +28,6 @@ class social_security__income_test_4(variables.Variable): default_value = 0 definition_period = periods.DateUnit.WEEK + @abstractmethod def formula_2018_11_26(families, period, params): - # Income Test 4 means that the applicable rate of [a] benefit must be - # reduced - - # List of people with families. - people = families.members - - # Where is this income-test being called from. - callers = [frame.filename for frame in inspect.stack()[5:7]] - - for benefit in IncomeTestedBenefit: - for caller in callers: - if benefit.name in caller: - applicable_rate = families.sum( - people(f"{benefit.name}__base", period), - role = entities.Family.PRINCIPAL, - ) - - # by 35 cents for every $1 of the total income of the - # beneficiary and the beneficiary’s spouse or partner that - # is more than $160 a week. - total_income = families(f"{benefit.name}__income", period) - - # Required for income tests as i.e. x¢ for every $y. - floor = numpy.floor(total_income) - - scale = ( - params(period) - .social_security - .dictionary - .income_test_4 - ) - - # The abatement rate regardless of benefit rate. - abatement_rate = scale.calc(floor) - - # The abatement rate capped at the applicable benefit rate. - return numpy.minimum(abatement_rate, applicable_rate) - - raise InvalidBenefitError(callers) - - -class InvalidBenefitError(NotImplementedError): - """Raise when a benefit is not valid for an income test.""" - - def __init__(self, callers): - message = "The provided files do not contain an income-tested benefit:" - super().__init__(os.linesep.join([message, *callers])) + raise NotImplementedError() diff --git a/openfisca_aotearoa/variables/acts/social_security/emergency_benefit/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/emergency_benefit/income_tests.py new file mode 100644 index 00000000..fd240561 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/emergency_benefit/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Emergency Benefit's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class emergency_benefit__income_test_1(dictionary.social_security__income_test_1): + label = "Emergency Benefit — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("emergency_benefit__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("emergency_benefit__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class emergency_benefit__income_test_2(dictionary.social_security__income_test_1): + label = "Emergency Benefit — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("emergency_benefit__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("emergency_benefit__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class emergency_benefit__income_test_3(dictionary.social_security__income_test_3): + label = "Emergency Benefit — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("emergency_benefit__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("emergency_benefit__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class emergency_benefit__income_test_4(dictionary.social_security__income_test_4): + label = "Emergency Benefit — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("emergency_benefit__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("emergency_benefit__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py index e40ce3b6..fdfab628 100644 --- a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/abatement.py @@ -44,9 +44,9 @@ def formula_2018_11_26(people, period, _params): + people("schedule_4__part1_1_h", period) ) - income_test_1 = families("social_security__income_test_1", period) - income_test_3 = families("social_security__income_test_3", period) - income_test_4 = families("social_security__income_test_4", period) + income_test_1 = families("jobseeker_support__income_test_1", period) + income_test_3 = families("jobseeker_support__income_test_3", period) + income_test_4 = families("jobseeker_support__income_test_4", period) return ( + case_1 * income_test_1 @@ -75,9 +75,9 @@ def formula_2020_11_09(people, period, _params): + people("schedule_4__part1_1_h", period) ) - income_test_1 = families("social_security__income_test_1", period) - income_test_3 = families("social_security__income_test_3", period) - income_test_4 = families("social_security__income_test_4", period) + income_test_1 = families("jobseeker_support__income_test_1", period) + income_test_3 = families("jobseeker_support__income_test_3", period) + income_test_4 = families("jobseeker_support__income_test_4", period) return ( + case_1 * income_test_1 diff --git a/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income_tests.py new file mode 100644 index 00000000..80403e75 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/jobseeker_support/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Jobseeker Support's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class jobseeker_support__income_test_1(dictionary.social_security__income_test_1): + label = "Jobseeker Support — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("jobseeker_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("jobseeker_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class jobseeker_support__income_test_2(dictionary.social_security__income_test_1): + label = "Jobseeker Support — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("jobseeker_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("jobseeker_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class jobseeker_support__income_test_3(dictionary.social_security__income_test_3): + label = "Jobseeker Support — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("jobseeker_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("jobseeker_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class jobseeker_support__income_test_4(dictionary.social_security__income_test_4): + label = "Jobseeker Support — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("jobseeker_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("jobseeker_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/income_tests.py new file mode 100644 index 00000000..491570f2 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Sole Parent Support's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class sole_parent_support__income_test_1(dictionary.social_security__income_test_1): + label = "Sole Parent Support — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("sole_parent_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("sole_parent_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class sole_parent_support__income_test_2(dictionary.social_security__income_test_1): + label = "Sole Parent Support — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("sole_parent_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("sole_parent_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class sole_parent_support__income_test_3(dictionary.social_security__income_test_3): + label = "Sole Parent Support — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("sole_parent_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("sole_parent_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class sole_parent_support__income_test_4(dictionary.social_security__income_test_4): + label = "Sole Parent Support — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("sole_parent_support__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("sole_parent_support__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py index 78c2f4ae..dffc3a7d 100644 --- a/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py +++ b/openfisca_aotearoa/variables/acts/social_security/sole_parent_support/sole_parent_support_benefit.py @@ -75,7 +75,7 @@ class sole_parent_support__abatement(variables.Variable): definition_period = periods.WEEK def formula_2018_11_26(people, period, _params): - return people.family("social_security__income_test_1", period) + return people.family("sole_parent_support__income_test_1", period) class sole_parent_support__income(variables.Variable): diff --git a/openfisca_aotearoa/variables/acts/social_security/superannuation/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/superannuation/income_tests.py new file mode 100644 index 00000000..ab47f5a4 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/superannuation/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Superannuation's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class superannuation__income_test_1(dictionary.social_security__income_test_1): + label = "Superannuation — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("superannuation__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("superannuation__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class superannuation__income_test_2(dictionary.social_security__income_test_1): + label = "Superannuation — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("superannuation__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("superannuation__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class superannuation__income_test_3(dictionary.social_security__income_test_3): + label = "Superannuation — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("superannuation__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("superannuation__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3a + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class superannuation__income_test_4(dictionary.social_security__income_test_4): + label = "Superannuation — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("superannuation__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("superannuation__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/supported_living_payment/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/supported_living_payment/income_tests.py new file mode 100644 index 00000000..b6230249 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/supported_living_payment/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Supported Living Payment's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class supported_living_payment__income_test_1(dictionary.social_security__income_test_1): + label = "Supported Living Payment — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("supported_living_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("supported_living_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class supported_living_payment__income_test_2(dictionary.social_security__income_test_1): + label = "Supported Living Payment — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("supported_living_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("supported_living_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class supported_living_payment__income_test_3(dictionary.social_security__income_test_3): + label = "Supported Living Payment — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("supported_living_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("supported_living_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class supported_living_payment__income_test_4(dictionary.social_security__income_test_4): + label = "Supported Living Payment — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("supported_living_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("supported_living_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/veterans_pension/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/veterans_pension/income_tests.py new file mode 100644 index 00000000..d5131131 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/veterans_pension/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Veteran's Pension's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class veterans_pension__income_test_1(dictionary.social_security__income_test_1): + label = "Veteran's Pension — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("veterans_pension__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("veterans_pension__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class veterans_pension__income_test_2(dictionary.social_security__income_test_1): + label = "Veteran's Pension — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("veterans_pension__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("veterans_pension__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class veterans_pension__income_test_3(dictionary.social_security__income_test_3): + label = "Veteran's Pension — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("veterans_pension__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("veterans_pension__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3a + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class veterans_pension__income_test_4(dictionary.social_security__income_test_4): + label = "Veteran's Pension — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("veterans_pension__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("veterans_pension__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/young_parent_payment/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/young_parent_payment/income_tests.py new file mode 100644 index 00000000..b5f9b490 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/young_parent_payment/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Youth Parent Payment's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class youth_parent_payment__income_test_1(dictionary.social_security__income_test_1): + label = "Youth Parent Payment — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_parent_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("youth_parent_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_parent_payment__income_test_2(dictionary.social_security__income_test_1): + label = "Youth Parent Payment — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_parent_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("youth_parent_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_parent_payment__income_test_3(dictionary.social_security__income_test_3): + label = "Youth Parent Payment — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_parent_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("youth_parent_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_parent_payment__income_test_4(dictionary.social_security__income_test_4): + label = "Youth Parent Payment — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_parent_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("youth_parent_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/acts/social_security/youth_payment/income_tests.py b/openfisca_aotearoa/variables/acts/social_security/youth_payment/income_tests.py new file mode 100644 index 00000000..de065803 --- /dev/null +++ b/openfisca_aotearoa/variables/acts/social_security/youth_payment/income_tests.py @@ -0,0 +1,192 @@ +"""This module provides the calculation of Youth Payment's Income Tests.""" + +import numpy + +from openfisca_core import periods + +from openfisca_aotearoa import entities +from openfisca_aotearoa.variables.acts.social_security import dictionary + + +class youth_payment__income_test_1(dictionary.social_security__income_test_1): + label = "Youth Payment — Income Test 1" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 1 means that the applicable rate of [a] benefit must be + reduced by 30 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 70 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 1 means that the applicable rate of [a] benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 30 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("youth_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 70 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_1 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_payment__income_test_2(dictionary.social_security__income_test_1): + label = "Youth Payment — Income Test 2" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 2 means that the applicable rate of [a] benefit must be + reduced by 15 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week + but not more than $250 a week; and by 35 cents for every $1 of that + income that is more than $250 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 2 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # (a) by 15 cents for every $1 of the total income of the beneficiary + # and the beneficiary’s spouse or partner that is more than $160 a + # week but not more than $250 a week; and + total_income = families("youth_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (b) by 35 cents for every $1 of that income that is more than $250 a + # week + scale = params(period).social_security.dictionary.income_test_2 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_payment__income_test_3(dictionary.social_security__income_test_3): + label = "Youth Payment — Income Test 3" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 3 means that the applicable rate of [a] benefit must be + reduced by 70 cents for every $1 of total income of the beneficiary and + the beneficiary’s spouse or partner that is more than, if the rate of + benefit is a rate of New Zealand superannuation stated in clause 1 of + Part 2 of Schedule 1 of the New Zealand Superannuation and Retirement + Income Act 2001, $160 a week; or in any other case, $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 3 means that the applicable rate of benefit must be + # reduced— + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 70 cents for every $1 of total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than,— + total_income = families("youth_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + # (a) if the rate of benefit is a rate of New Zealand + # superannuation stated in clause 1 of Part 2 of + # Schedule 1 of the New Zealand Superannuation and + # Retirement Income Act 2001, $160 a week; or + # (b) in any other case, $160 a week + scale = params(period).social_security.dictionary.income_test_3b + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) + + +class youth_payment__income_test_4(dictionary.social_security__income_test_4): + label = "Youth Payment — Income Test 4" + reference = "https://www.legislation.govt.nz/act/public/2018/0032/latest/whole.html#DLM6784375" + documentation = """ + Income Test 4 means that the applicable rate of [a] benefit must be + reduced by 35 cents for every $1 of the total income of the beneficiary + and the beneficiary’s spouse or partner that is more than $160 a week. + """ + entity = entities.Family + value_type = float + default_value = 0 + definition_period = periods.DateUnit.WEEK + + def formula_2018_11_26(families, period, params): + # Income Test 4 means that the applicable rate of [a] benefit must be + # reduced + + # List of people with families. + people = families.members + + applicable_rate = families.sum( + people("youth_payment__base", period), + role = entities.Family.PRINCIPAL, + ) + + # by 35 cents for every $1 of the total income of the + # beneficiary and the beneficiary’s spouse or partner that + # is more than $160 a week. + total_income = families("youth_payment__income", period) + + # Required for income tests as i.e. x¢ for every $y. + floor = numpy.floor(total_income) + + scale = params(period).social_security.dictionary.income_test_4 + + # The abatement rate regardless of benefit rate. + abatement_rate = scale.calc(floor) + + # The abatement rate capped at the applicable benefit rate. + return numpy.minimum(abatement_rate, applicable_rate) diff --git a/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py b/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py deleted file mode 100644 index 75f75eb4..00000000 --- a/openfisca_aotearoa/variables/exegesis/income_tested_benefit.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Exegesis — Income-tested benefit. - -Income-tested benefit is a benefit subject to an income-test. While the term -has been repealed in favour of "main benefit", we use it to refer to any -benefit that is subject to an income-test, whether it is a main benefit or not. - -For example, the Accommodation Supplement is not a main benefit, but it is, -under certain conditions, subject to an income-test. - -""" - -from openfisca_core import indexed_enums - - -class IncomeTestedBenefit(indexed_enums.Enum): - jobseeker_support = "jobseeker_support" - sole_parent_support = "sole_parent_support" - superannuation = "superannuation" - veterans_pension = "veterans_pension"