Support threshold uprating in scale parameters#507
Merged
Conversation
Scale thresholds could only be uprated via the per-leaf form `brackets[i].threshold.metadata.uprating` or a scale-wide `uprating`+`uprate_thresholds` pair. Bracket-level metadata and a threshold-specific scale index were silently ignored, so bend points and indexed brackets froze at their last explicit value. Resolve uprating for each bracket component leaf from, in priority order: the leaf's own metadata; a bracket per-component override (`brackets[i].metadata.threshold.uprating`); a bracket default (`brackets[i].metadata.uprating` for the value side, plus `threshold_uprating`/`uprate_thresholds` for the threshold); and finally the scale-level defaults, now including `metadata.threshold_uprating` for a threshold-only index. The working per-leaf form is unchanged and locked with regression tests. Fixes #390 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…prating # Conflicts: # policyengine_core/parameters/parameter_scale_bracket.py
Contributor
Author
|
Fable review: TDD reproductions of every broken form, four-level resolution precedence with the value-side/threshold distinction preserved (bare uprating never silently hits thresholds, consistent with scale-level semantics), the documented interpretation of bare bracket uprating flagged for the issue author, and the post-#506 rebase iterates component keys correctly. 608 core tests + 39 country-template green; the issue-body PIA example verified uprating (1226 → 1284.38). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #390.
Problem
Scale (marginal-rate / amount) parameters only uprated thresholds through two placements:
brackets[i].threshold.metadata.uprating, andmetadata.upratingcombined withmetadata.uprate_thresholds: true.Every other placement was silently dead, so bend points and indexed brackets froze at their last explicit value:
brackets[i].metadata.uprating(the amount stayed frozen — policyengine-us#8814),brackets[i].metadata.threshold.upratingsub-key form,metadata.threshold_upratingdedicated to thresholds.(The controlled experiments confirming which forms worked are in the #390 comments.)
Change
ParameterScaleBracket.propagate_upratingnow resolves anupratingentry for each component leaf, andParameterScale.propagate_upratingpasses through the scale-levelthreshold_uprating. Resolution order per component, highest priority first:metadata.uprating(the existing per-leaf form — unchanged);brackets[i].metadata.threshold.uprating/brackets[i].metadata.amount.uprating;brackets[i].metadata.upratingfor the value side (amount/rate/base/average_rate), and for the threshold eitherbrackets[i].metadata.threshold_upratingor the bare bracketupratingwhenbrackets[i].metadata.uprate_thresholdsis set;metadata.uprating(gated byuprate_thresholdsfor the threshold) and the newmetadata.threshold_upratingfor a threshold-only index.This matches the issue's proposed solution:
threshold_upratingin scale metadata,upratingin individual threshold metadata, and both (individual overriding the scale default).Semantics I had to interpret
The issue and its comments show two shapes for bracket-level metadata: a bare
brackets[i].metadata.uprating(expected to uprate the amount, per policyengine-us#8814) and an explicitbrackets[i].metadata.threshold.uprating(to uprate the threshold). I read a bare bracketupratingas value-side only — mirroring the existing scale-level rule where a bareupratingnever touches thresholds unlessuprate_thresholdsopts them in. The threshold is therefore reached only via thethresholdsub-key, a bracket/scalethreshold_uprating, or anuprate_thresholdsflag. If a bare bracketupratingwere instead meant to hit the threshold too, that is a one-line change — flagging the assumption explicitly.uprate_parametersalready reaches every bracket leaf throughget_descendants(locked with a dedicated test), so no change was needed there; the gap was purely in propagation.Tests
New
tests/core/parameters/operations/test_scale_threshold_uprating.py(13 tests): the three previously-working forms are locked as regressions, each newly-supported placement is covered, plusthreshold_upratingcombined with a distinct amount index, per-leaf override of the scale default, threshold-index leaving amounts frozen, andget_descendantsbracket-leaf coverage. Also verified the exact PIA example from the issue body uprates (bend point 1226 → 1284.38 in 2026).Checks run
uv run pytest tests -m "not smoke"→ 599 passed, 1 skipped, 1 xfailed.policyengine-core teston the bundled country template → 39 passed.ruff format(clean) andruff check(passed).-m smokesuite (requires network / GitHub microdata token).Note on overlap with #506
Independent of the sibling PR #506 (issue #505). #506 renames the bracket's component-key tuple from
_allowed_keysto_component_keysand adds the common keys for validation; this PR keeps iterating_allowed_keys. Whichever merges second will have a trivial rename conflict inparameter_scale_bracket.py.🤖 Generated with Claude Code