Skip to content

Implement CPP retirement pension#525

Open
MaxGhenis wants to merge 4 commits intoPolicyEngine:masterfrom
MaxGhenis:implement-cpp-benefits
Open

Implement CPP retirement pension#525
MaxGhenis wants to merge 4 commits intoPolicyEngine:masterfrom
MaxGhenis:implement-cpp-benefits

Conversation

@MaxGhenis
Copy link
Contributor

Summary

Changes

  • Added CPP retirement pension parameters (maximum, average monthly amounts, eligibility age)
  • Implemented eligibility based on age 60+ and contribution history
  • Calculate benefit proportional to years of contribution (up to 40 years)
  • Use average monthly benefit as basis for simplified calculation

Test Coverage

  • Full contribution scenarios (40 years)
  • Partial contribution scenarios
  • Age eligibility tests
  • Multi-year test cases (2024 and 2025)

Simplifications

This is a simplified implementation that:

  • Uses years of contribution as a simple input variable
  • Calculates benefit as a proportion of the average benefit based on contribution years
  • Does not implement early/late retirement adjustments
  • Does not implement the complex earnings-based calculation

References

🤖 Generated with Claude Code

@PavelMakarchuk
Copy link
Collaborator

Program Review: CPP Retirement Pension (PR #525)

Program: Canada Pension Plan (CPP) -- Retirement Pension
Branch: implement-cpp-benefits
PR: #525
Review Date: 2026-03-05
Files Changed: 8 (7 new + 1 modified)


Critical (Must Fix)

  1. Formula uses population average instead of earnings-based calculation. The pension formula contribution_factor * p.average_monthly multiplies a statistical average of all current recipients by years/40. Under the CPP Act (Section 46), benefits are 25% of the individual's average monthly pensionable earnings. This means a 40-year maximum contributor gets $816.52/month instead of the correct $1,364.60/month (2024). The model cannot differentiate between low and high earners. The maximum_monthly parameter is defined but never used in any formula. Either adopt an earnings-based approximation (e.g., min(0.25 * earnings_proxy, p.maximum_monthly)) or at minimum make the full-contribution case produce the maximum, not the average. -- cpp_retirement_pension.py, maximum_monthly.yaml

  2. Branch is extremely stale -- needs rebase on master. The PR branch uses setup.py with version 0.96.3 while master uses pyproject.toml with version 0.97.3. The PR deletes changelog.d/ files added after its branch point and carries outdated workflow files. This is the root cause of CI version-check and lint failures. -- setup.py, .github/workflows/, changelog.d/

  3. 2025 average monthly value ($844.53) contradicts official figure ($803.76). The canada.ca payment amounts page shows $803.76 as the October 2025 average. The PR value of $844.53 is unverifiable and higher than the official figure. Additionally, the 2021 value ($702.77) being lower than 2020 ($710.41) is unusual -- averages rarely decrease year over year. -- average_monthly.yaml

  4. Historical parameter values (2020-2024) cannot be corroborated from cited reference. The sole reference (canada.ca amounts page) only displays the most recent values. Historical values for both average_monthly and maximum_monthly cannot be verified by clicking the provided link. Add year-specific references from ESDC statistical publications or the Canada Gazette. -- average_monthly.yaml, maximum_monthly.yaml

  5. Hard-coded 40 in formula (maximum contributory years). The value 40 represents the maximum contributory period and is a policy parameter that could change. It should be extracted to a parameter file (max_contributory_years.yaml) with a reference to CPP legislation, not hard-coded in the formula. -- cpp_retirement_pension.py, line 23

  6. Missing changelog fragment. No changelog.d/525.added.md file exists for this PR. Create one with content: "Added CPP retirement pension benefit calculation."

  7. Test file in wrong directory. The test is at tests/benefits/cpp/ but the repository convention is tests/gov/cra/benefits/cpp/. Move to match existing test file structure. -- policyengine_canada/tests/benefits/cpp/cpp_retirement_pension.yaml


Should Address

  1. documentation field used instead of reference in all three variable files. PolicyEngine variables should include a reference field pointing to the authoritative government source URL. The documentation field is a free-text docstring that does not serve this purpose. -- cpp_retirement_eligible.py, cpp_retirement_pension.py, cpp_years_of_contribution.py

  2. Parameter descriptions do not follow PolicyEngine conventions. Descriptions should be complete sentences using approved verbs (limits, provides, sets) and the "this X" placeholder pattern. E.g., "The Canada Pension Plan program provides this amount as the average monthly retirement pension at age 65." -- all 3 parameter YAML files

  3. Reference titles are too vague. All three parameter files use generic titles like "CPP retirement pension amount". Titles should be specific enough to tell a reviewer what to look for without clicking the link (e.g., "Canada.ca -- CPP retirement pension: How much you could receive -- Maximum monthly amount at age 65"). -- all 3 parameter YAML files

  4. Missing trailing newlines on all new files. All 8 new files are missing a trailing newline character. This is a minor code quality issue flagged by most linters and diff tools.

  5. Simplification disclaimers in production code. Comments say "Simplified calculation" and "For simplicity, use average as midpoint." Either implement the actual formula or document the simplification as a known limitation in the variable metadata, not as inline comments. -- cpp_retirement_pension.py

  6. No early/late retirement adjustment. The implementation treats ages 60 through 70+ identically. In reality, CPP applies a 0.6%/month reduction before age 65 (up to 36% at age 60) and a 0.7%/month increase after 65 (up to 42% at age 70). This is a significant factor that should at minimum be filed as a follow-up issue.


Suggestions

  1. Add year-specific references for historical values. Service Canada publishes monthly statistical reports. Cite the specific ESDC/Service Canada publication for each historical year, or use the Government of Canada's historical CPP payment rates.

  2. Add section anchors to reference URLs. If canada.ca pages have section IDs, include them in the href (e.g., amount.html#average, amount.html#maximum).

  3. Add separate unit tests for cpp_retirement_eligible. Currently only the pension amount is tested. Eligibility logic (age threshold, contribution requirement) should have its own test file.

  4. Add edge case tests: age exactly 59, age 60 with zero contributions (both conditions interact), fractional contribution years.

  5. Consider the * 12 conversion factor. While 12 months/year is a universal constant, adding a brief comment or named constant (MONTHS_IN_YEAR = 12) would follow the pattern of keeping formulas free of unexplained numeric literals.

  6. Consider rounding. Test case Canada Child Benefit #6 produces $244.956. In production, CPP is paid in whole cents. Consider whether rounding should be applied.

  7. Department path (CRA vs ESDC). CPP is administered by ESDC/Service Canada, not CRA. However, the existing codebase already places OAS under gov/cra/benefits/. Accept for consistency now but consider filing a separate issue to reorganize under gov/esdc/.

  8. Confirm reference placement in parameter YAML. The reference key appears at the top level outside metadata. Verify this matches the repo's schema convention (standard PolicyEngine nests it inside metadata).


Validation Summary

Check Result
Eligibility age (60) matches legislation PASS
Contribution requirement (>0 years) matches regulation PASS
Maximum monthly values (2020-2025) plausible PASS (unverifiable from cited source)
Average monthly values accurate FAIL -- 2025 contradicts official figure
Formula matches CPP Act Section 46 FAIL -- uses population average, not individual earnings
maximum_monthly parameter used in formula FAIL -- defined but unused
Hard-coded values extracted to parameters FAIL -- 40 hard-coded
Vectorized operations (where, min_) PASS
Correct entity (Person) PASS
Period handling (YEAR) PASS
adds list updated in benefits.py PASS
Test file location matches repo convention FAIL -- wrong directory
Trailing newlines present FAIL -- all files missing
Changelog fragment present FAIL -- missing
Branch up to date with master FAIL -- extremely stale
CI passing FAIL -- version check and lint fail
Test coverage (6 cases) PARTIAL -- good basics, missing edge cases
Parameter references verifiable PARTIAL -- eligibility age OK, amounts not

Review Severity: REQUEST_CHANGES

Blocking issues: The formula design (using population average instead of individual earnings -- Critical #1) produces fundamentally misleading results for any non-average recipient. The stale branch (Critical #2) causes CI failures and will produce merge conflicts. These two issues must be resolved before this PR can be merged.

Test coverage score: 6/10 -- Tests are well-structured and mathematically verified for the current (simplified) formula, but miss edge cases and lack separate eligibility tests.

Recommended path forward:

  1. Rebase onto current master to fix CI
  2. Redesign the formula to be earnings-based (or clearly cap at maximum_monthly)
  3. Verify and fix the 2025 average monthly value
  4. Address the remaining critical items (hard-coded 40, changelog, test location)
  5. Clean up warnings (documentation vs reference, descriptions, trailing newlines)

MaxGhenis and others added 3 commits March 5, 2026 18:29
- Add CPP retirement pension parameters (maximum, average, eligibility age)
- Implement eligibility based on age 60+ and contribution history
- Calculate benefit proportional to years of contribution (up to 40 years)
- Use average monthly benefit as basis for calculation
- Add test coverage for various age and contribution scenarios

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add cpp_retirement_pension to household benefits aggregation
- Remove unnecessary self-referential adds declaration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Cap benefit at maximum_monthly: min_(avg * factor, max) * 12
- Extract hard-coded 40 into max_contributory_years parameter
- Fix 2025 average monthly: $844.53 → $803.76 per canada.ca
- Add reference URLs to all 3 variable files
- Move tests to tests/gov/cra/benefits/cpp/ (correct path)
- Add changelog fragment
- Update parameter descriptions to active-voice format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@PavelMakarchuk PavelMakarchuk force-pushed the implement-cpp-benefits branch from be4ae6d to d05b442 Compare March 5, 2026 17:34
- Fix max_contributory_years href to CPP Act s. 49 (page-11)
- Add approximation note explaining 40-year simplification
- Make all reference titles specific (Act section, data type)
- Add historical data sourcing notes to average and maximum params

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants