diff --git a/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/gis_spa_couple_plateau_rate.yaml b/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/gis_spa_couple_plateau_rate.yaml new file mode 100644 index 000000000..b1df74019 --- /dev/null +++ b/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/gis_spa_couple_plateau_rate.yaml @@ -0,0 +1,10 @@ +description: GIS reduction rate for the 'plateau' range for a GIS recipient married to a SPA recipient. +values: + 2021-01-01: 0 +metadata: + period: year + label: GIS reduction rate for the 'plateau' range for a GIS recipient married to a SPA recipient. + documentataion: Corresponds to GISRRM and GISRLM in the SPSD/M Variable Guide + reference: + - title: SPSD/M Variable Guide + diff --git a/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/two_pensioners.yaml b/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/two_pensioners.yaml index 22d170bbb..09dd06050 100644 --- a/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/two_pensioners.yaml +++ b/policyengine_canada/parameters/gov/cra/benefits/gis_spa/gis_reduction/two_pensioners.yaml @@ -1,4 +1,4 @@ -description: Reduction threshold and rate for pensioners married to pensioners. Applies to COUPLE_BOTH_OAS or COUPLE_ONE_OAS_SPA_ELIGIBLE. Note that this calculates on couples combined income, whereas the other GIS cateogires calculate on the lone pensioner's income. Correponds to GISRRM in the SPSD/M. +description: Reduction threshold and rate for pensioners married to pensioners. Applies to COUPLE_BOTH_OAS. Note that this calculates on couples combined income, whereas the other GIS cateogires calculate on the lone pensioner's income. Correponds to GISRRM in the SPSD/M. brackets: - threshold: 2021-01-01: 0 diff --git a/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction.yaml b/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction.yaml index c915ef8d4..29a9c8ceb 100644 --- a/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction.yaml +++ b/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction.yaml @@ -6,7 +6,7 @@ output: gis_reduction: 100 -- name: OAS pensioner whose spouse is SPA eligible +- name: OAS pensioner whose spouse is SPA eligible, with combined income too low to trigger the plateau in the reduction period: 2023 input: gis_spa_category: "COUPLE_ONE_OAS_SPA_ELIGIBLE" @@ -16,6 +16,17 @@ output: gis_reduction: 1_000 +- name: Couple with combined income that exceeds the upper plateau threshold given by breakeven_spa_eligible. So the GIS reduction starts again after the plateau. + period: 2023 + absolute_error_margin: .01 + input: + gis_spa_category: "COUPLE_ONE_OAS_SPA_ELIGIBLE" + individual_net_income: 20_048 + spouse_net_income: 20_000 + gis_cap: 30000 + output: + gis_reduction: 7487.68 + - name: Two OAS pensioners period: 2023 input: diff --git a/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction_spa_couple.yaml b/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction_spa_couple.yaml new file mode 100644 index 000000000..e09d74f58 --- /dev/null +++ b/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_reduction_spa_couple.yaml @@ -0,0 +1,42 @@ +- name: Couple with combined income less than the plateau threshold. + period: 2023 + absolute_error_margin: .01 + input: + gis_spa_category: "COUPLE_ONE_OAS_SPA_ELIGIBLE" + individual_net_income: 2_048 + spouse_net_income: 2_000 + output: + gis_reduction_spa_couple: 1_000 + +- name: Couple with combined income inside the plateau threshold. So the GIS reduction is frozen at the reduction you would have if your income were the crossover amount. + period: 2023 + absolute_error_margin: .01 + input: + gis_spa_category: "COUPLE_ONE_OAS_SPA_ELIGIBLE" + individual_net_income: 15_048 + spouse_net_income: 15_000 + output: + gis_reduction_spa_couple: 6_469.86 + +- name: Couple with combined income that exceeds the upper plateau threshold given by breakeven_spa_eligible. So the GIS reduction starts again after the plateau. + period: 2023 + absolute_error_margin: .01 + input: + gis_spa_category: "COUPLE_ONE_OAS_SPA_ELIGIBLE" + individual_net_income: 20_048 + spouse_net_income: 20_000 + output: + gis_reduction_spa_couple: 7487.68 + +- name: Automatically 0 if you're not the right gis-spa category + period: 2023 + absolute_error_margin: .01 + input: + gis_spa_category: "COUPLE_ONE_OAS_SPA_INELIGIBLE" + individual_net_income: 20_048 + spouse_net_income: 20_000 + output: + gis_reduction_spa_couple: 0 + + + diff --git a/policyengine_canada/tests/gov/cra/benefits/gis_spa/breakeven_spa_eligible.yaml b/policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_spa_couple_thresholds.yaml similarity index 100% rename from policyengine_canada/tests/gov/cra/benefits/gis_spa/breakeven_spa_eligible.yaml rename to policyengine_canada/tests/gov/cra/benefits/gis_spa/gis_spa_couple_thresholds.yaml diff --git a/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction.py b/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction.py index e3cca1981..21e519eda 100644 --- a/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction.py +++ b/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction.py @@ -15,19 +15,20 @@ def formula(person, period, parameters): household = person.household spouse_net_income = household("spouse_net_income", period) gis_base = person("gis_cap", period) + gis_reduction_spa_couple = person("gis_reduction_spa_couple", period) p = parameters(period).gov.cra.benefits.gis_spa.gis_reduction reduction = select( [ gis_spa_category == gis_spa_categories.SINGLE_WITH_OAS, gis_spa_category == gis_spa_categories.COUPLE_BOTH_OAS, - (gis_spa_category == gis_spa_categories.COUPLE_ONE_OAS_SPA_ELIGIBLE) & (gis_base > 0), # THIS NEEDS TO CHANGE BASED ON THE 'CROSSOVER' THING. # the gis_base > 0 makes sure this person is the eligible one in the couple, since both people in the couple will have the same category. + (gis_spa_category == gis_spa_categories.COUPLE_ONE_OAS_SPA_ELIGIBLE) & (gis_base > 0), # the gis_base > 0 makes sure this person is the eligible one in the couple, since both people in the couple will have the same category. (gis_spa_category == gis_spa_categories.COUPLE_ONE_OAS_SPA_INELIGIBLE) & (gis_base > 0) ], [ p.one_pensioner.calc(individual_net_income), p.two_pensioners.calc(individual_net_income + spouse_net_income), - p.two_pensioners.calc(individual_net_income + spouse_net_income), + gis_reduction_spa_couple, p.one_pensioner.calc(individual_net_income) ], default=0, diff --git a/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction_spa_couple.py b/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction_spa_couple.py new file mode 100644 index 000000000..cbcfca83d --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis/gis_reduction_spa_couple.py @@ -0,0 +1,35 @@ +from policyengine_canada.model_api import * + +class gis_reduction_spa_couple(Variable): + value_type = float + entity = Person + label = "Guaranteed Income Supplement Reduction for a Recipient with SPA-eligible Spouse" + unit = CAD + documentation = "The amount by which the base GIS is reduced, based on personal net income and the number of pensioners in the household. This is implemented as a variable, not a parameter, due to the complexity and dependencies of the reduction rate." + definition_period = YEAR + + def formula(person, period, parameters): + gis_spa_category = person("gis_spa_category", period) + gis_spa_categories = gis_spa_category.possible_values + individual_net_income = person("individual_net_income", period) + household = person.household + spouse_net_income = household("spouse_net_income", period) + combined_net_income = individual_net_income + spouse_net_income + state = person.state + crossover = state("gis_spa_crossover", period) + breakeven_spa_eligible = state("breakeven_spa_eligible", period) + breakeven_spa_ineligible = state("breakeven_spa_ineligible", period) + plateau_range = breakeven_spa_eligible - crossover + p = parameters(period).gov.cra.benefits.gis_spa.gis_reduction + + # Reduce like other pensioner couples up to the crossover point + first_part = min(p.two_pensioners.calc(combined_net_income), p.two_pensioners.calc(crossover)) + # Plateau until the spouse's SPA-gis-portion is exhausted, as captured by the variable plateau_range. The first part must be maxed-out for this to be non-zero. + second_part = min((combined_net_income - first_part) * p.gis_spa_couple_plateau_rate, plateau_range * p.gis_spa_couple_plateau_rate) * (first_part == (p.two_pensioners.calc(crossover))) + # Start reducing again at the married couple breakeven point for income beyond the crossover maximum. Only be non-zero if there's any income in that range. + third_part = p.two_pensioners.calc(combined_net_income - (crossover - 48) - plateau_range) * ((combined_net_income > (crossover - plateau_range))) + + # return(third_part) + return(first_part + second_part + third_part) * (gis_spa_category == gis_spa_categories.COUPLE_ONE_OAS_SPA_ELIGIBLE) + + diff --git a/policyengine_canada/variables/gov/cra/benefits/gis_spa/crossover.py b/policyengine_canada/variables/gov/cra/benefits/gis_spa/gis_spa_crossover.py similarity index 100% rename from policyengine_canada/variables/gov/cra/benefits/gis_spa/crossover.py rename to policyengine_canada/variables/gov/cra/benefits/gis_spa/gis_spa_crossover.py