From 9df1c8ea9ed1c311e381f5270dd1e1ee90310e92 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 24 Aug 2025 16:07:37 -0400 Subject: [PATCH 1/2] Implement Employment Insurance (EI) benefits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add EI benefit rate and maximum weekly benefit parameters for 2020-2025 - Implement EI eligibility based on minimum hours requirement (420 hours) - Calculate weekly benefit as 55% of average insurable earnings, capped at maximum - Simplify annual benefit to 26 weeks (midpoint of 14-45 week range) - Add comprehensive test coverage for various income scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../maximum_weekly_benefit.yaml | 17 ++++++ .../employment_insurance/minimum_hours.yaml | 10 ++++ .../benefits/employment_insurance/rate.yaml | 10 ++++ .../employment_insurance/ei_benefit.yaml | 56 +++++++++++++++++++ .../employment_insurance/ei_benefit.py | 20 +++++++ .../employment_insurance/ei_eligible.py | 22 ++++++++ .../ei_insurable_earnings.py | 21 +++++++ .../ei_insurable_hours.py | 10 ++++ .../employment_insurance/ei_weekly_benefit.py | 29 ++++++++++ 9 files changed, 195 insertions(+) create mode 100644 policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_weekly_benefit.yaml create mode 100644 policyengine_canada/parameters/gov/cra/benefits/employment_insurance/minimum_hours.yaml create mode 100644 policyengine_canada/parameters/gov/cra/benefits/employment_insurance/rate.yaml create mode 100644 policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml create mode 100644 policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_benefit.py create mode 100644 policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_eligible.py create mode 100644 policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py create mode 100644 policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_hours.py create mode 100644 policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_weekly_benefit.py diff --git a/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_weekly_benefit.yaml b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_weekly_benefit.yaml new file mode 100644 index 000000000..07ac4680a --- /dev/null +++ b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_weekly_benefit.yaml @@ -0,0 +1,17 @@ +description: Maximum weekly Employment Insurance benefit amount +values: + 2020-01-01: 573 + 2021-01-01: 595 + 2022-01-01: 638 + 2023-01-01: 650 + 2024-01-01: 668 + 2025-01-01: 695 +metadata: + unit: currency-CAD + period: week + label: EI maximum weekly benefit + reference: + - title: Employment Insurance benefit amounts + href: https://www.canada.ca/en/services/benefits/ei/ei-regular-benefit/benefit-amount.html + - title: EI premium rates and maximums + href: https://www.canada.ca/en/revenue-agency/services/tax/businesses/topics/payroll/payroll-deductions-contributions/employment-insurance-ei/ei-premium-rates-maximums.html \ No newline at end of file diff --git a/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/minimum_hours.yaml b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/minimum_hours.yaml new file mode 100644 index 000000000..fd05bea90 --- /dev/null +++ b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/minimum_hours.yaml @@ -0,0 +1,10 @@ +description: Minimum insurable hours required for Employment Insurance eligibility +values: + 2020-01-01: 420 +metadata: + unit: hour + period: year + label: EI minimum insurable hours + reference: + - title: Employment Insurance eligibility + href: https://www.canada.ca/en/services/benefits/ei/ei-regular-benefit/eligibility.html \ No newline at end of file diff --git a/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/rate.yaml b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/rate.yaml new file mode 100644 index 000000000..5ef8bdac5 --- /dev/null +++ b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/rate.yaml @@ -0,0 +1,10 @@ +description: Employment Insurance benefit replacement rate +values: + 2020-01-01: 0.55 +metadata: + unit: /1 + period: year + label: EI benefit rate + reference: + - title: Employment Insurance benefit amounts + href: https://www.canada.ca/en/services/benefits/ei/ei-regular-benefit/benefit-amount.html \ No newline at end of file diff --git a/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml b/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml new file mode 100644 index 000000000..1a941bdd0 --- /dev/null +++ b/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml @@ -0,0 +1,56 @@ +- name: Basic EI benefit calculation + period: 2024 + input: + employment_income: 50_000 + ei_insurable_hours: 600 + output: + # Weekly: min($50,000, $63,200) / 52 * 0.55 = $528.85 + # Capped at maximum $668/week, so $528.85 + # Annual: $528.85 * 26 weeks = $13,750 + ei_benefit: 13_750 + +- name: EI benefit at maximum + period: 2024 + input: + employment_income: 80_000 + ei_insurable_hours: 1_000 + output: + # Weekly: min($80,000, $63,200) / 52 * 0.55 = $668 (at maximum) + # Annual: $668 * 26 weeks = $17,368 + ei_benefit: 17_368 + +- name: Below minimum hours - no benefit + period: 2024 + input: + employment_income: 40_000 + ei_insurable_hours: 400 + output: + ei_benefit: 0 + +- name: No employment income - no benefit + period: 2024 + input: + employment_income: 0 + ei_insurable_hours: 600 + output: + ei_benefit: 0 + +- name: EI benefit for 2025 + period: 2025 + input: + employment_income: 60_000 + ei_insurable_hours: 800 + output: + # Weekly: $60,000 / 52 * 0.55 = $634.62 + # Annual: $634.62 * 26 weeks = $16,500 + ei_benefit: 16_500 + +- name: EI benefit at 2025 maximum + period: 2025 + input: + employment_income: 90_000 + ei_insurable_hours: 1_200 + output: + # Weekly: min($90,000, $65,700) / 52 * 0.55 = $695 (at maximum) + # Annual: $695 * 26 weeks = $18,070 + ei_benefit: 18_070 \ No newline at end of file diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_benefit.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_benefit.py new file mode 100644 index 000000000..b42912f71 --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_benefit.py @@ -0,0 +1,20 @@ +from policyengine_canada.model_api import * + + +class ei_benefit(Variable): + value_type = float + entity = Person + label = "Employment Insurance benefit" + definition_period = YEAR + unit = CAD + documentation = "Annual Employment Insurance benefit amount" + adds = ["ei_benefit"] + + def formula(person, period, parameters): + # Get the weekly benefit amount + weekly_benefit = person("ei_weekly_benefit", period) + + # For simplicity, assume 26 weeks of benefits (halfway between min 14 and max 45) + weeks_of_benefits = 26 + + return weekly_benefit * weeks_of_benefits \ No newline at end of file diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_eligible.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_eligible.py new file mode 100644 index 000000000..61c1a2ecd --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_eligible.py @@ -0,0 +1,22 @@ +from policyengine_canada.model_api import * + + +class ei_eligible(Variable): + value_type = bool + entity = Person + label = "Eligible for Employment Insurance" + definition_period = YEAR + documentation = "Whether person is eligible for Employment Insurance benefits" + + def formula(person, period, parameters): + p = parameters(period).gov.cra.benefits.employment_insurance + + # Check minimum hours requirement + insurable_hours = person("ei_insurable_hours", period) + meets_hours = insurable_hours >= p.minimum_hours + + # Must have insurable earnings + insurable_earnings = person("ei_insurable_earnings", period) + has_earnings = insurable_earnings > 0 + + return meets_hours & has_earnings \ No newline at end of file diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py new file mode 100644 index 000000000..86139ab73 --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py @@ -0,0 +1,21 @@ +from policyengine_canada.model_api import * + + +class ei_insurable_earnings(Variable): + value_type = float + entity = Person + label = "Employment Insurance insurable earnings" + definition_period = YEAR + unit = CAD + documentation = "Total insurable earnings in the qualifying period" + + def formula(person, period, parameters): + employment_income = person("employment_income", period) + + # For now, use the maximum weekly benefit to infer max insurable earnings + # Maximum insurable earnings = max_weekly_benefit / rate + p = parameters(period).gov.cra.benefits.employment_insurance + max_insurable = p.maximum_weekly_benefit / p.rate * 52 + + # Cap at maximum insurable earnings + return min_(employment_income, max_insurable) \ No newline at end of file diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_hours.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_hours.py new file mode 100644 index 000000000..2b5e557ef --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_hours.py @@ -0,0 +1,10 @@ +from policyengine_canada.model_api import * + + +class ei_insurable_hours(Variable): + value_type = float + entity = Person + label = "Employment Insurance insurable hours" + definition_period = YEAR + unit = "hour" + documentation = "Number of insurable hours worked in the qualifying period" \ No newline at end of file diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_weekly_benefit.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_weekly_benefit.py new file mode 100644 index 000000000..4fbb7bab0 --- /dev/null +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_weekly_benefit.py @@ -0,0 +1,29 @@ +from policyengine_canada.model_api import * + + +class ei_weekly_benefit(Variable): + value_type = float + entity = Person + label = "Employment Insurance weekly benefit" + definition_period = YEAR + unit = CAD + documentation = "Weekly Employment Insurance benefit amount (calculated annually)" + + def formula(person, period, parameters): + p = parameters(period).gov.cra.benefits.employment_insurance + + # Check eligibility + eligible = person("ei_eligible", period) + + # Calculate average weekly earnings + # Simplified - using annual earnings divided by 52 + insurable_earnings = person("ei_insurable_earnings", period) + weekly_earnings = insurable_earnings / 52 + + # Apply benefit rate + benefit_amount = weekly_earnings * p.rate + + # Cap at maximum + capped_benefit = min_(benefit_amount, p.maximum_weekly_benefit) + + return where(eligible, capped_benefit, 0) \ No newline at end of file From 0ef90e51f7752c36cbaff1978cb90b520626add5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 24 Aug 2025 21:46:22 -0400 Subject: [PATCH 2/2] Fix EI implementation issues identified by reviewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add maximum_insurable_earnings parameter with official values - Use direct parameter reference instead of indirect calculation - Add ei_benefit to household benefits aggregation - Fix test expected value for 2025 maximum scenario 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../maximum_insurable_earnings.yaml | 15 +++++++++++++++ .../benefits/employment_insurance/ei_benefit.yaml | 6 +++--- policyengine_canada/variables/gov/benefits.py | 1 + .../employment_insurance/ei_insurable_earnings.py | 6 +----- 4 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_insurable_earnings.yaml diff --git a/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_insurable_earnings.yaml b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_insurable_earnings.yaml new file mode 100644 index 000000000..71c4a255c --- /dev/null +++ b/policyengine_canada/parameters/gov/cra/benefits/employment_insurance/maximum_insurable_earnings.yaml @@ -0,0 +1,15 @@ +description: Maximum annual insurable earnings for Employment Insurance +values: + 2020-01-01: 54_200 + 2021-01-01: 56_300 + 2022-01-01: 60_300 + 2023-01-01: 61_500 + 2024-01-01: 63_200 + 2025-01-01: 65_700 +metadata: + unit: currency-CAD + period: year + label: EI maximum insurable earnings + reference: + - title: EI premium rates and maximums + href: https://www.canada.ca/en/revenue-agency/services/tax/businesses/topics/payroll/payroll-deductions-contributions/employment-insurance-ei/ei-premium-rates-maximums.html \ No newline at end of file diff --git a/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml b/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml index 1a941bdd0..08f38d394 100644 --- a/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml +++ b/policyengine_canada/tests/benefits/employment_insurance/ei_benefit.yaml @@ -51,6 +51,6 @@ employment_income: 90_000 ei_insurable_hours: 1_200 output: - # Weekly: min($90,000, $65,700) / 52 * 0.55 = $695 (at maximum) - # Annual: $695 * 26 weeks = $18,070 - ei_benefit: 18_070 \ No newline at end of file + # Weekly: min($90,000, $65,700) / 52 * 0.55 = $694.90 + # Annual: $694.90 * 26 weeks = $18,067.50 + ei_benefit: 18_067.50 \ No newline at end of file diff --git a/policyengine_canada/variables/gov/benefits.py b/policyengine_canada/variables/gov/benefits.py index 052eae5ed..ee081cca0 100644 --- a/policyengine_canada/variables/gov/benefits.py +++ b/policyengine_canada/variables/gov/benefits.py @@ -12,6 +12,7 @@ class benefits(Variable): "child_disability_benefit", "canada_workers_benefit", "dental_benefit", + "ei_benefit", "oas_net", # Ontario programs. "on_benefits", diff --git a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py index 86139ab73..e3ce741a7 100644 --- a/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py +++ b/policyengine_canada/variables/gov/cra/benefits/employment_insurance/ei_insurable_earnings.py @@ -11,11 +11,7 @@ class ei_insurable_earnings(Variable): def formula(person, period, parameters): employment_income = person("employment_income", period) - - # For now, use the maximum weekly benefit to infer max insurable earnings - # Maximum insurable earnings = max_weekly_benefit / rate p = parameters(period).gov.cra.benefits.employment_insurance - max_insurable = p.maximum_weekly_benefit / p.rate * 52 # Cap at maximum insurable earnings - return min_(employment_income, max_insurable) \ No newline at end of file + return min_(employment_income, p.maximum_insurable_earnings) \ No newline at end of file