Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/first_estimates_birth_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@
Path("src/populace_dynamics/cohorts/age67.py"),
Path("src/populace_dynamics/estimates/adjusted_poverty.py"),
Path("src/populace_dynamics/estimates/uniform_cut_tabulation.py"),
# The opt-in statutory AIME (42 USC 415(b)(2) computation years) sits
# beside the sealed ss.benefits, which it calls but does not edit; the
# historical ledger never imports it.
Path("src/populace_dynamics/ss/statutory_aime.py"),
)
POST_REVIEW_SHARED_SOURCE_BLOBS = {
Path(
Expand Down
776 changes: 646 additions & 130 deletions scripts/track_c_aime_agreement.py

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions src/populace_dynamics/cola_track_a/benefits.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@
Levels the oracle does not compute (decision 2(b) and the A6
pre-eligibility-death hook) follow :class:`LevelPolicy`;
:func:`approximate_pia` is the disclosed approximation.

Benefit computation years (:data:`TRACK_A_COMPUTATION_YEARS`). Every
oracle AIME in this module divides by 35 years
(:attr:`~populace_dynamics.ss.statutory_aime.ComputationYears.
LEGACY_FIXED_35`, the unchanged ``ss.benefits.aime``), the arithmetic of
Track A's Registration 13 (``runs/replication_urban2010_cola_v1.json``,
registered commit ``002264d9``). 42 USC 415(b)(2) gives fewer years to
workers born before 1929 (elapsed years less 5); the Track C step 1
comparison (2026-09-23) traced all 251 of its AIME disagreements, among
7486 people, to that count. The assembly keeps the count Registration 13
used; moving a future Track A run to the statutory count is a
registration-level choice that has not been made.
"""

from __future__ import annotations
Expand All @@ -90,8 +102,10 @@
from populace_dynamics.engine.loop import ProjectionResult
from populace_dynamics.ss import benefits
from populace_dynamics.ss.params import SSAParameters
from populace_dynamics.ss.statutory_aime import ComputationYears

__all__ = [
"TRACK_A_COMPUTATION_YEARS",
"BenefitContext",
"StateLookups",
"PiaRecord",
Expand All @@ -102,6 +116,11 @@

#: The oracle's PIA formula covers eligibility from 1979 (plan section 10).
FIRST_ORACLE_ELIGIBILITY_YEAR = 1979
#: The AIME computation-year convention of Track A's oracle retirement
#: level: the legacy fixed 35 that Registration 13 was computed with (module
#: docstring), not the statutory 415(b)(2) count. :func:`approximate_pia`
#: is always legacy.
TRACK_A_COMPUTATION_YEARS = ComputationYears.LEGACY_FIXED_35
_RETIREMENT_AGE = 62
_MONTHS = 12
_OPENING_AUX = ("survivor", "spouse", "other", "unclassified")
Expand Down Expand Up @@ -163,6 +182,11 @@ def approximate_pia(
It is not the statutory DI or pre-eligibility-death computation: the
divisor is always 35 years (no elapsed or dropout years), so it
understates short careers. It sets a weight, never a reform ratio.
It always calls the unchanged ``benefits.aime``
(``ComputationYears.LEGACY_FIXED_35``), the approximation Track A
registered. The statutory counts for a disability onset or a death
before 62 exist as ``ss.statutory_aime.aime(..., disability_year=...)``
or ``death_year=...``; using them here would change that approximation.
"""

kept = {
Expand Down Expand Up @@ -257,6 +281,7 @@ def _level(
history=self._history(person_id),
birth_year=birth,
params=self.ctx.params,
computation_years=TRACK_A_COMPUTATION_YEARS,
)
else:
self.cache[key] = approximate_pia(
Expand Down
36 changes: 28 additions & 8 deletions src/populace_dynamics/scenario_benefits.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
benefit level of a worker who died before eligibility is likewise a
raising hook (:func:`survivor_of_preeligibility_death_benefit_level`).
Callers may supply any PIA to the path functions; this module does not
say where a DI or survivor PIA comes from.
say where a DI or survivor PIA comes from. (The statutory computation
years for those cases exist in :mod:`populace_dynamics.ss.statutory_aime`;
neither hook uses them without a ruling.)
* No family maximum, earnings test, recomputation, payment-month timing,
whole-dollar payment rounding, or pre-1983 June-effective COLA timing.
The annual convention is the sealed ledger's: the amount for payment
Expand Down Expand Up @@ -90,6 +92,14 @@
plan names no specific approximation, so there is no proposed primary
to default to; a caller may still pass any PIA to the path functions.

The age-62 AIME's benefit computation years are an explicit parameter of
:func:`eligibility_pia_for_clock` (``computation_years``). The default is
the statute, :attr:`~populace_dynamics.ss.statutory_aime.ComputationYears.
STATUTORY` (42 USC 415(b)(2): elapsed years less 5, fewer than 35 for
workers born before 1929). ``LEGACY_FIXED_35`` is the sealed ledger's
``benefits.aime`` (always 35), which Track A's Registration 13 used and
which the Track A assembly still passes explicitly.

One convention inside the ``ENTITLEMENT`` alternative is fixed here and
not named in the plan, so the specification freeze (plan item A1) must
confirm it: for a spouse or widow(er), "entitlement" is the insured
Expand All @@ -112,8 +122,9 @@

from populace_dynamics.estimates.ledgers import REPORT_YEARS, floor_to_dime
from populace_dynamics.estimates.parameters import COLASeries
from populace_dynamics.ss import benefits
from populace_dynamics.ss import benefits, statutory_aime
from populace_dynamics.ss.params import SSAParameters
from populace_dynamics.ss.statutory_aime import ComputationYears

__all__ = [
"DI_BENEFIT_LEVEL_RULING",
Expand Down Expand Up @@ -777,14 +788,21 @@ def eligibility_pia_for_clock(
history: Mapping[int, float],
birth_year: int,
params: SSAParameters,
computation_years: ComputationYears = ComputationYears.STATUTORY,
) -> float:
"""The PIA at eligibility for the worker on ``clock``.

Only the old-age case is computed, with the existing oracle exactly as
the sealed ledger calls it (``benefits.aime`` then ``benefits.pia`` in
the year the worker attains 62). ``history`` is used as supplied; the
caller applies any cutoff law. The other bases route to hooks that
raise ``NotImplementedError``.
Only the old-age case is computed: the oracle AIME, then
``benefits.pia`` in the year the worker attains 62. The AIME counts
its benefit computation years by ``computation_years``
(:func:`populace_dynamics.ss.statutory_aime.oracle_aime`):
``STATUTORY`` (the default) follows 42 USC 415(b)(2) and equals the
sealed ledger's ``benefits.aime`` for workers born 1929 or later; it
refuses workers attaining 62 before 1975 and history years before
1951. ``LEGACY_FIXED_35`` is ``benefits.aime`` exactly as the sealed
ledger calls it, for every birth year. ``history`` is used as
supplied; the caller applies any cutoff law. The other bases route to
hooks that raise ``NotImplementedError``.
"""

birth = _year(birth_year, "birth_year")
Expand All @@ -798,7 +816,9 @@ def eligibility_pia_for_clock(
raise ValueError(
"An age-62 clock must start in the year the worker attains 62."
)
aime_value = benefits.aime(dict(history), birth, params)
aime_value = statutory_aime.oracle_aime(
history, birth, params, computation_years=computation_years
)
return benefits.pia(aime_value, clock.eligibility_year, params)


Expand Down
Loading
Loading