Skip to content

Fix Maine PTFC senior benefit base (mis-typed marginal scale) (#8813)#8814

Merged
MaxGhenis merged 3 commits into
PolicyEngine:mainfrom
DTrim99:fix-me-ptfc-senior-benefit-base
Jul 5, 2026
Merged

Fix Maine PTFC senior benefit base (mis-typed marginal scale) (#8813)#8814
MaxGhenis merged 3 commits into
PolicyEngine:mainfrom
DTrim99:fix-me-ptfc-senior-benefit-base

Conversation

@DTrim99

@DTrim99 DTrim99 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8813.

Problem

The Maine Property Tax Fairness Credit under-computed the credit for filers whose older of head/spouse is exactly age 65+ (tax years 2024+): the enhanced senior benefit base silently never applied.

Root cause

parameters/.../fairness/property_tax/benefit_base/senior.yaml was missing metadata.type: single_amount, so its brackets loaded as a MarginalAmountTaxScale instead of a SingleAmountTaxScale. The marginal scale treats the bracket threshold as exclusive, so senior.calc(65) returned 0 at the exact threshold. The guard in me_property_tax_fairness_credit_base_cap (senior_benefit_base != 0) then fell back to the non-senior single base.

(This is why the existing age-66 test passed — above the threshold the marginal scale coincidentally returns the right amount; only age exactly 65 was broken.)

Fix

  • Add type: single_amount (plus threshold_unit: year / amount_unit: currency-USD, matching the sibling cap.yaml) to senior.yaml.
  • Add an age-exactly-65 regression test to me_property_tax_fairness_credit_base_cap.yaml: 2025 single filer, income $60,000, countable rent property tax $5,000 → base cap $1,700 (senior base $4,100 − 4% × income). Before the fix this returned $150 (fallback to the $2,550 single base).

References

  • 2025 Form 1040ME Schedule PTFC/STFC (lines 7-9, 12)
  • 36 MRS §5219-KK

🤖 Generated with Claude Code

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Fix Maine PTFC senior benefit base (mis-typed marginal scale)

Good catch and a correct, minimal fix. Requesting changes mainly to fix a pre-existing uprating bug that lives in the exact metadata block this PR edits — cheap to fix now — plus a wrong reference link and two test additions.

Verified correct (the fix)

  • Adding type: single_amount makes senior.calc(65) return $4,100 (inclusive threshold), $0 below 65, unchanged at 66+, and $0 pre-2024 — verified empirically against SingleAmountTaxScale.calc. The bug was isolated to exactly age 65 (the marginal scale returned $0 there, tripping the senior_benefit_base != 0 fallback to the non-senior base). Metadata now matches sibling property_tax/cap.yaml; the incorrect pre-fix unit: currency-USD is correctly removed.
  • Values confirmed against Maine sources: senior base $4,100 (2025 Schedule PTFC/STFC line 8) / $4,000 (2024, §5219-KK(1)(A-1)(4)), non-senior single $2,550, 4% rate, age 65+ (one spouse suffices), effective TY2024+. Regression test (age 65, 2025, $60k, $5k → $1,700 vs pre-fix $150) discriminates the senior path correctly; 9/9 tests pass.

Requested changes

  1. Fix the senior base's uprating while you're in this block (pre-existing, but in-scope now). senior.yaml:14-18 declares uprating at the bracket level (uprating: gov.irs.uprating with a sibling rounding: key) instead of the working nested form used in single.yaml (uprating: {parameter: gov.irs.uprating, rounding: {...}}). Effect: the senior base stays flat at $4,100 for 2026+ instead of inflation-adjusting per 36 M.R.S. §5403, whereas single.yaml correctly steps 2,550 → 2,600 → … It's latent on main (not a regression) and doesn't affect the explicit 2024/2025 values — but since this PR is already rewriting senior.yaml's scale metadata, converting the uprating to the working nested form here avoids leaving the senior base understated in later years.
  2. Reference fix: the "2024 Form 1040ME" entry (senior.yaml) links to 23_1040me_sched_pstfc_ff.pdf — the same URL as the 2023 entry. Point it at the actual 2024 form PDF (locate the real filename on maine.gov/revenue; a naive 24_… guess 404s).
  3. Tests:
    • The age-64 case doesn't isolate the base (its output is driven by the under-65 cap, so it'd pass even if the senior base wrongly applied at 64). Add a discriminating age-64 mirror of the age-65 case (2025, $60k, $5k → $150).
    • Add an MFJ / one-spouse-65+ case (e.g. head 60 / spouse 67, 2025) to exercise the greater_age_head_spouse selection.

Suggestions

  • Replace the senior_benefit_base != 0 sentinel in me_property_tax_fairness_credit_base_cap.py:19 with an explicit age / in-effect check for clarity (functionally correct today).
  • senior.yaml description verb "allows for" isn't in the approved verb list (limits/provides/sets/excludes/deducts/uses) — pre-existing.
  • Add absolute_error_margin to the older cases (1-4) for consistency with the newer ones.

🤖 Reviewed with Claude Code

DTrim99 added a commit to DTrim99/policyengine-us that referenced this pull request Jul 2, 2026
…ence, boundary tests

- Convert the senior benefit-base bracket uprating to the working nested
  form so it inflation-adjusts for 2026+ per 36 M.R.S. §5403 (the flat form
  with a sibling rounding key silently left it flat at $4,100).
- Repoint the 2024 Form 1040ME reference from the 2023 PDF to the actual
  2024 Schedule PTFC/STFC PDF (verified live; shows $4,000 senior base).
- Add a discriminating age-64 test (->$150, senior base does not apply) and
  an MFJ one-spouse-67 test (->$1,700, senior base selected via
  greater_age_head_spouse); add absolute_error_margin to the older cases;
  fix the description verb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Fixes applied (addressing @PavelMakarchuk's review)

Pushed as 2b424edf04.

Requested changes

  • Senior-base uprating — converted the bracket-level uprating from the flat form (uprating: gov.irs.uprating + sibling rounding:, which silently left the base flat at $4,100) to the working nested form (uprating: {parameter: gov.irs.uprating, rounding: {…}}), matching single.yaml. Kept inside the bracket metadata so only the amount uprates (not the age threshold). The senior base now inflation-adjusts for 2026+ per 36 M.R.S. §5403.
  • 2024 reference — the "2024 Form 1040ME" entry pointed at the 2023 PDF; repointed it to the actual 2024 Schedule PTFC/STFC (24_Form 1040ME_Sch PTFC_ff.pdf#page=2, verified live — shows "$2,000 ($4,000 if 65 or older)").
  • Tests — added a discriminating age-64 case (2025, $60k, $5k → $150; senior base does not apply, so it can't pass if the base wrongly applied at 64) and an MFJ one-spouse-67 case (head 60 / spouse 67 → $1,700, exercising greater_age_head_spouse).

Suggestions

  • Added absolute_error_margin to the older cases (1–4) and fixed the description verb ("allows for" → "provides").
  • Left the senior_benefit_base != 0 sentinel in …_base_cap.py as-is (functionally correct today, per your note).

CI running now.

@DTrim99 DTrim99 requested a review from PavelMakarchuk July 2, 2026 15:39
DTrim99 and others added 3 commits July 5, 2026 08:34
…yEngine#8813)

benefit_base/senior.yaml lacked metadata.type: single_amount, so its brackets
loaded as a MarginalAmountTaxScale whose calc() treats the threshold as
exclusive. At the exact age-65 threshold it returned 0 instead of $4,100, so
me_property_tax_fairness_credit_base_cap silently fell back to the non-senior
single base and under-computed the credit for 65+ filers (tax years 2024+).

Add type: single_amount (matching cap.yaml) plus threshold_unit/amount_unit, and
add an age-exactly-65 regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ence, boundary tests

- Convert the senior benefit-base bracket uprating to the working nested
  form so it inflation-adjusts for 2026+ per 36 M.R.S. §5403 (the flat form
  with a sibling rounding key silently left it flat at $4,100).
- Repoint the 2024 Form 1040ME reference from the 2023 PDF to the actual
  2024 Schedule PTFC/STFC PDF (verified live; shows $4,000 senior base).
- Add a discriminating age-64 test (->$150, senior base does not apply) and
  an MFJ one-spouse-67 test (->$1,700, senior base selected via
  greater_age_head_spouse); add absolute_error_margin to the older cases;
  fix the description verb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous revision moved the `uprating:` block into the *bracket's*
metadata (`brackets[1].metadata.uprating`). The core uprater only reads
`uprating` off leaf Parameter nodes (or a scale-level `metadata.uprating`
that it propagates to bracket amounts via ParameterScale.propagate_uprating);
a block on the bracket node itself is silently ignored. Empirically the
senior base stayed flat at $4,100 for 2026+ despite the block, which is the
exact freezing bug the review asked to fix.

Nest `uprating:` inside the `amount:` leaf's own metadata (using the
`values:`/`metadata:` split form the parser requires for a dated leaf). The
senior amount now uprates by gov.irs.uprating, floored to $50: 2025 $4,100
-> 2026 $4,150 -> 2027 $4,300, while the age-65 threshold stays fixed
(thresholds only uprate under `uprate_thresholds`). Verified end-to-end
through the full system, not just a manual uprate call.

Add a 2026 regression test (age 66, $60k income, $5k rent -> $1,750) that
discriminates a working uprating from a base frozen at $4,100 (which yields
$1,700). Rebased onto main (post PolicyEngine#8907/PolicyEngine#8908); the code-health guard
tests/code_health/test_uprating_placement.py stays green (this file uses the
scale `brackets` structure, not the `values`/`uprating` sibling form the
guard flags).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis force-pushed the fix-me-ptfc-senior-benefit-base branch from 2b424ed to 9e19995 Compare July 5, 2026 12:47
@MaxGhenis

Copy link
Copy Markdown
Contributor

Rebased onto main + finished the review items (pushed 9e19995174)

Rebased onto current main (now includes #8907 and #8908) and worked through the open review points. Rebase was clean — zero conflicts. #8908 relocated the sibling-of-values uprating: blocks in the sales-tax fairness files (.../fairness/sales_tax/amount/base.yaml, .../reduction/start.yaml), not this property-tax benefit_base/senior.yaml; and main hadn't touched senior.yaml, the PTFC base-cap test, or the changelog fragment since this branch forked, so nothing overlapped.

Guard test reconciliation (test_uprating_placement.py)

It stays green. That check flags only nodes with values and uprating as direct siblings; senior.yaml is a single_amount scale (brackets: with threshold/amount sub-nodes), which the check doesn't (and shouldn't) touch — so #8908's new guard and this file don't collide.

One substantive correction beyond the earlier push

The earlier revision put uprating: under the bracket's metadata (brackets[1].metadata.uprating). That position is silently ignored: the core uprater (uprate_parameters.py) only reads uprating off leaf Parameter nodes, or a scale-level metadata.uprating that ParameterScale.propagate_uprating pushes down to bracket amounts — never off the bracket node itself. Verified empirically: the senior base stayed flat at $4,100 for 2026+ even with that block present, i.e. the freeze you flagged was still there.

Fixed by nesting uprating: inside the amount: leaf's own metadata, using the values:/metadata: split the parser requires for a dated leaf:

  - threshold:
      2018-01-01: 65
    amount:
      values:
        2018-01-01: 0
        2024-01-01: 4_000
        2025-01-01: 4_100
      metadata:
        uprating:
          parameter: gov.irs.uprating
          rounding:
            type: downwards
            interval: 50

Verified end-to-end through the full system (not just a manual uprate_parameter call):

Year Senior base (age 65+)
2025 $4,100
2026 $4,150
2027 $4,300
2028 $4,350

The age-65 threshold stays fixed (thresholds only uprate under uprate_thresholds), so only the amount indexes — which matches the intent of keeping it "inside the bracket so only the amount uprates." Added a 2026 regression test (age 66, $60k income, $5k rent → $1,750) that discriminates a working uprating from a base frozen at $4,100 (which would give $1,700).

Status of each requested change

  1. Senior-base uprating — now genuinely inflation-adjusts for 2026+ (was still frozen after the prior push; see above). ✅
  2. 2024 reference — repointed to 24_Form%201040ME_Sch%20PTFC_ff.pdf#page=2. Verified live: header reads "2024 Form 1040ME, Schedule PTFC/STFC, page 2", Line 8 says "If line 7 is yes, enter $4,000", non-senior single $2,450, senior cap $2,000 (Line 12), age 65+ one-spouse-suffices (Line 7). ✅
  3. Tests — age-64 discriminating case (→ $150) and MFJ one-spouse-67 case (→ $1,700) present from the prior push; added the 2026 uprating-regression case (→ $1,750). ✅

Suggestions

  • absolute_error_margin on cases 1–4 and the description verb ("allows for" → "provides") were handled in the prior push. ✅
  • Left the senior_benefit_base != 0 sentinel in me_property_tax_fairness_credit_base_cap.py as-is, per your note that it's functionally correct today.

Tests

  • Full ME baseline directory: 550 passed.
  • property_tax_fairness_credit/ directory: 33 passed (base-cap file now 12 cases).
  • test_uprating_placement.py: passed; test_parameter_files.py (6) and test_system_import.py (5): passed.
  • ruff format --check / ruff check: clean (no .py touched).

The changelog fragment now notes both the age-65 threshold fix and the 2026+ uprating fix.

@MaxGhenis

Copy link
Copy Markdown
Contributor

CI note

Everything relevant to this change is green — both baseline ME shards (states-non-ny-shard-1/2), the changed-file selective run, lint, changelog, and smoke-imports all pass.

The one red check, Full Suite - Contrib (states-shard-2), is a pre-existing CI-infra flake unrelated to this PR. It runs contrib state-reform structural YAML tests (policy/contrib/states, shard 2/2) — nothing this PR touches (the change is confined to baseline/gov/states/me/ + the uprating-placement guard). The failing run's own log shows a runner shutdown, not a test failure:

##[error]The runner has received a shutdown signal...
##[error]The operation was canceled.

The same shard is currently failing on main at 104363a786 (the commit this branch is rebased onto) and on a later main commit — i.e. it's red on main independent of this PR. I've re-triggered it; if it's still red after the runner retry it's a main-CI issue to resolve separately and safe to --admin-merge past for this PR.

@MaxGhenis MaxGhenis dismissed PavelMakarchuk’s stale review July 5, 2026 14:59

All requested items verified implemented (see the resolution comment on this thread); dismissing so the verified state can merge.

@MaxGhenis MaxGhenis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fable review + agent verification: the author's reference and test asks were verified done; the uprating ask needed real work — the bracket-level metadata placement was empirically dead (flat $4,100 through 2028), and the fix nests it in the amount leaf (verified $4,150/2026, $4,300/2027 exact index math) with a re-freeze regression test. 550 ME tests green; guard test green.

@MaxGhenis MaxGhenis merged commit 979041e into PolicyEngine:main Jul 5, 2026
53 of 54 checks passed
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.

Maine PTFC senior benefit base never applies (parameter mis-typed as marginal scale)

3 participants