@retiregolden/engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +79 -0
- package/dist/allocation/assetClasses.d.ts +94 -0
- package/dist/allocation/assetClasses.js +215 -0
- package/dist/decisions/annuitization.d.ts +72 -0
- package/dist/decisions/annuitization.js +184 -0
- package/dist/decisions/decisionFixtures.d.ts +48 -0
- package/dist/decisions/decisionFixtures.js +253 -0
- package/dist/decisions/evaluateCandidate.d.ts +61 -0
- package/dist/decisions/evaluateCandidate.js +193 -0
- package/dist/decisions/generators.d.ts +110 -0
- package/dist/decisions/generators.js +667 -0
- package/dist/decisions/index.d.ts +17 -0
- package/dist/decisions/index.js +17 -0
- package/dist/decisions/insightsAdapter.d.ts +27 -0
- package/dist/decisions/insightsAdapter.js +51 -0
- package/dist/decisions/objectives.d.ts +96 -0
- package/dist/decisions/objectives.js +319 -0
- package/dist/decisions/pensionElection.d.ts +112 -0
- package/dist/decisions/pensionElection.js +239 -0
- package/dist/decisions/search.d.ts +51 -0
- package/dist/decisions/search.js +110 -0
- package/dist/decisions/spendingSolver.d.ts +70 -0
- package/dist/decisions/spendingSolver.js +180 -0
- package/dist/decisions/spiaQuotes.d.ts +21 -0
- package/dist/decisions/spiaQuotes.js +43 -0
- package/dist/decisions/stochastic.d.ts +11 -0
- package/dist/decisions/stochastic.js +66 -0
- package/dist/decisions/swrComparator.d.ts +54 -0
- package/dist/decisions/swrComparator.js +89 -0
- package/dist/decisions/tournament.d.ts +53 -0
- package/dist/decisions/tournament.js +104 -0
- package/dist/decisions/types.d.ts +125 -0
- package/dist/decisions/types.js +12 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/insights/detectors/annuitizationHeadroom.d.ts +11 -0
- package/dist/insights/detectors/annuitizationHeadroom.js +77 -0
- package/dist/insights/detectors/assetLocation.d.ts +9 -0
- package/dist/insights/detectors/assetLocation.js +86 -0
- package/dist/insights/detectors/hecmBufferCandidate.d.ts +12 -0
- package/dist/insights/detectors/hecmBufferCandidate.js +76 -0
- package/dist/insights/detectors/incomeFloorFunded.d.ts +8 -0
- package/dist/insights/detectors/incomeFloorFunded.js +52 -0
- package/dist/insights/detectors/irmaaTierEdge.d.ts +2 -0
- package/dist/insights/detectors/irmaaTierEdge.js +102 -0
- package/dist/insights/detectors/pensionElectionPending.d.ts +11 -0
- package/dist/insights/detectors/pensionElectionPending.js +62 -0
- package/dist/insights/detectors/qcdEfficiency.d.ts +2 -0
- package/dist/insights/detectors/qcdEfficiency.js +62 -0
- package/dist/insights/detectors/rothBridgeHeadroom.d.ts +2 -0
- package/dist/insights/detectors/rothBridgeHeadroom.js +77 -0
- package/dist/insights/detectors/spendingGuardrails.d.ts +2 -0
- package/dist/insights/detectors/spendingGuardrails.js +77 -0
- package/dist/insights/detectors/spendingHeadroom.d.ts +12 -0
- package/dist/insights/detectors/spendingHeadroom.js +92 -0
- package/dist/insights/detectors/ssBridgeGap.d.ts +9 -0
- package/dist/insights/detectors/ssBridgeGap.js +92 -0
- package/dist/insights/detectors/stateRelocation.d.ts +12 -0
- package/dist/insights/detectors/stateRelocation.js +99 -0
- package/dist/insights/detectors/widowsPenalty.d.ts +12 -0
- package/dist/insights/detectors/widowsPenalty.js +145 -0
- package/dist/insights/registry.d.ts +4 -0
- package/dist/insights/registry.js +62 -0
- package/dist/insights/runInsights.d.ts +6 -0
- package/dist/insights/runInsights.js +15 -0
- package/dist/insights/types.d.ts +70 -0
- package/dist/insights/types.js +1 -0
- package/dist/ladder/bridge.d.ts +72 -0
- package/dist/ladder/bridge.js +59 -0
- package/dist/ladder/fedInvest.d.ts +55 -0
- package/dist/ladder/fedInvest.js +77 -0
- package/dist/ladder/fundedRatio.d.ts +41 -0
- package/dist/ladder/fundedRatio.js +46 -0
- package/dist/ladder/ladderMath.d.ts +88 -0
- package/dist/ladder/ladderMath.js +140 -0
- package/dist/longevity/ssaPeriod2022.d.ts +10 -0
- package/dist/longevity/ssaPeriod2022.js +51 -0
- package/dist/longevity/types.d.ts +39 -0
- package/dist/longevity/types.js +1 -0
- package/dist/model/migrations.d.ts +20 -0
- package/dist/model/migrations.js +67 -0
- package/dist/model/plan.d.ts +3108 -0
- package/dist/model/plan.js +1514 -0
- package/dist/montecarlo/frontiers.d.ts +22 -0
- package/dist/montecarlo/frontiers.js +61 -0
- package/dist/montecarlo/historicalReturns.d.ts +27 -0
- package/dist/montecarlo/historicalReturns.js +124 -0
- package/dist/montecarlo/historicalSuites.d.ts +45 -0
- package/dist/montecarlo/historicalSuites.js +102 -0
- package/dist/montecarlo/ltcShock.d.ts +42 -0
- package/dist/montecarlo/ltcShock.js +62 -0
- package/dist/montecarlo/marketModels.d.ts +248 -0
- package/dist/montecarlo/marketModels.js +672 -0
- package/dist/montecarlo/mortality.d.ts +31 -0
- package/dist/montecarlo/mortality.js +61 -0
- package/dist/montecarlo/riskBasedGuardrails.d.ts +93 -0
- package/dist/montecarlo/riskBasedGuardrails.js +200 -0
- package/dist/montecarlo/rng.d.ts +18 -0
- package/dist/montecarlo/rng.js +44 -0
- package/dist/montecarlo/run.d.ts +210 -0
- package/dist/montecarlo/run.js +353 -0
- package/dist/montecarlo/sharedPaths.d.ts +43 -0
- package/dist/montecarlo/sharedPaths.js +28 -0
- package/dist/montecarlo/survival.d.ts +49 -0
- package/dist/montecarlo/survival.js +126 -0
- package/dist/params/data/realYieldCurve2026.d.ts +17 -0
- package/dist/params/data/realYieldCurve2026.js +26 -0
- package/dist/params/data/year2026.d.ts +14 -0
- package/dist/params/data/year2026.js +199 -0
- package/dist/params/index.d.ts +63 -0
- package/dist/params/index.js +134 -0
- package/dist/params/provenance.d.ts +29 -0
- package/dist/params/provenance.js +123 -0
- package/dist/params/state/data/year2026.d.ts +18 -0
- package/dist/params/state/data/year2026.js +581 -0
- package/dist/params/state/index.d.ts +13 -0
- package/dist/params/state/index.js +26 -0
- package/dist/params/state/types.d.ts +73 -0
- package/dist/params/state/types.js +10 -0
- package/dist/params/types.d.ts +201 -0
- package/dist/params/types.js +7 -0
- package/dist/projection/annuityForms.d.ts +62 -0
- package/dist/projection/annuityForms.js +87 -0
- package/dist/projection/compare.d.ts +96 -0
- package/dist/projection/compare.js +216 -0
- package/dist/projection/flatTax.d.ts +7 -0
- package/dist/projection/flatTax.js +14 -0
- package/dist/projection/optimizePlan.d.ts +328 -0
- package/dist/projection/optimizePlan.js +1039 -0
- package/dist/projection/relocation.d.ts +149 -0
- package/dist/projection/relocation.js +317 -0
- package/dist/projection/simulate.d.ts +61 -0
- package/dist/projection/simulate.js +2754 -0
- package/dist/projection/types.d.ts +381 -0
- package/dist/projection/types.js +9 -0
- package/dist/rmd/jointLifeTable.d.ts +8 -0
- package/dist/rmd/jointLifeTable.js +663 -0
- package/dist/rmd/rmd.d.ts +29 -0
- package/dist/rmd/rmd.js +34 -0
- package/dist/scenarios/scenarios.d.ts +70 -0
- package/dist/scenarios/scenarios.js +130 -0
- package/dist/socialSecurity/benefitFactor.d.ts +13 -0
- package/dist/socialSecurity/benefitFactor.js +40 -0
- package/dist/socialSecurity/claimFactor.d.ts +33 -0
- package/dist/socialSecurity/claimFactor.js +58 -0
- package/dist/socialSecurity/disability.d.ts +36 -0
- package/dist/socialSecurity/disability.js +42 -0
- package/dist/socialSecurity/familyMaximum.d.ts +38 -0
- package/dist/socialSecurity/familyMaximum.js +48 -0
- package/dist/socialSecurity/maritalBenefits.d.ts +57 -0
- package/dist/socialSecurity/maritalBenefits.js +87 -0
- package/dist/socialSecurity/nra.d.ts +30 -0
- package/dist/socialSecurity/nra.js +83 -0
- package/dist/socialSecurity/piaFromEarnings.d.ts +100 -0
- package/dist/socialSecurity/piaFromEarnings.js +202 -0
- package/dist/socialSecurity/ssaWageData.d.ts +68 -0
- package/dist/socialSecurity/ssaWageData.js +306 -0
- package/dist/socialSecurity/survivorBenefit.d.ts +61 -0
- package/dist/socialSecurity/survivorBenefit.js +61 -0
- package/dist/spending/abw.d.ts +57 -0
- package/dist/spending/abw.js +80 -0
- package/dist/spending/flexibleGoals.d.ts +62 -0
- package/dist/spending/flexibleGoals.js +136 -0
- package/dist/spending/guardrails.d.ts +78 -0
- package/dist/spending/guardrails.js +100 -0
- package/dist/spending/layers.d.ts +81 -0
- package/dist/spending/layers.js +67 -0
- package/dist/spending/shapePresets.d.ts +42 -0
- package/dist/spending/shapePresets.js +77 -0
- package/dist/strategies/accountEligibility.d.ts +81 -0
- package/dist/strategies/accountEligibility.js +98 -0
- package/dist/strategies/inheritedIra.d.ts +45 -0
- package/dist/strategies/inheritedIra.js +43 -0
- package/dist/strategies/iraBasis.d.ts +48 -0
- package/dist/strategies/iraBasis.js +46 -0
- package/dist/strategies/optimizer.d.ts +307 -0
- package/dist/strategies/optimizer.js +551 -0
- package/dist/strategies/rothBasis.d.ts +67 -0
- package/dist/strategies/rothBasis.js +83 -0
- package/dist/strategies/rothConversion.d.ts +55 -0
- package/dist/strategies/rothConversion.js +83 -0
- package/dist/strategies/sepp.d.ts +38 -0
- package/dist/strategies/sepp.js +53 -0
- package/dist/tax/aca.d.ts +40 -0
- package/dist/tax/aca.js +82 -0
- package/dist/tax/federalTax.d.ts +110 -0
- package/dist/tax/federalTax.js +249 -0
- package/dist/tax/medicare.d.ts +20 -0
- package/dist/tax/medicare.js +36 -0
- package/dist/tax/propertySale.d.ts +46 -0
- package/dist/tax/propertySale.js +35 -0
- package/dist/tax/stateTax.d.ts +66 -0
- package/dist/tax/stateTax.js +198 -0
- package/dist/testing/money.d.ts +7 -0
- package/dist/testing/money.js +17 -0
- package/dist/testing/planFixtures.d.ts +29 -0
- package/dist/testing/planFixtures.js +141 -0
- package/package.json +75 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official SSA series for wage indexing, bend points, family maximum bend
|
|
3
|
+
* points, and taxable maximum.
|
|
4
|
+
* @see https://www.ssa.gov/oact/COLA/awiseries.html
|
|
5
|
+
* @see https://www.ssa.gov/oact/COLA/bendpoints.html
|
|
6
|
+
* @see https://www.ssa.gov/oact/COLA/cbb.html
|
|
7
|
+
*/
|
|
8
|
+
/** National average wage index (AWI), dollars. */
|
|
9
|
+
export const AWI_BY_YEAR = {
|
|
10
|
+
1951: 2799.16,
|
|
11
|
+
1952: 2973.32,
|
|
12
|
+
1953: 3139.44,
|
|
13
|
+
1954: 3155.64,
|
|
14
|
+
1955: 3301.44,
|
|
15
|
+
1956: 3532.36,
|
|
16
|
+
1957: 3641.72,
|
|
17
|
+
1958: 3673.8,
|
|
18
|
+
1959: 3855.8,
|
|
19
|
+
1960: 4007.12,
|
|
20
|
+
1961: 4086.76,
|
|
21
|
+
1962: 4291.4,
|
|
22
|
+
1963: 4396.64,
|
|
23
|
+
1964: 4576.32,
|
|
24
|
+
1965: 4658.72,
|
|
25
|
+
1966: 4938.36,
|
|
26
|
+
1967: 5213.44,
|
|
27
|
+
1968: 5571.76,
|
|
28
|
+
1969: 5893.76,
|
|
29
|
+
1970: 6186.24,
|
|
30
|
+
1971: 6497.08,
|
|
31
|
+
1972: 7133.8,
|
|
32
|
+
1973: 7580.16,
|
|
33
|
+
1974: 8030.76,
|
|
34
|
+
1975: 8630.92,
|
|
35
|
+
1976: 9226.48,
|
|
36
|
+
1977: 9779.44,
|
|
37
|
+
1978: 10556.03,
|
|
38
|
+
1979: 11479.46,
|
|
39
|
+
1980: 12513.46,
|
|
40
|
+
1981: 13773.1,
|
|
41
|
+
1982: 14531.34,
|
|
42
|
+
1983: 15239.24,
|
|
43
|
+
1984: 16135.07,
|
|
44
|
+
1985: 16822.51,
|
|
45
|
+
1986: 17321.82,
|
|
46
|
+
1987: 18426.51,
|
|
47
|
+
1988: 19334.04,
|
|
48
|
+
1989: 20099.55,
|
|
49
|
+
1990: 21027.98,
|
|
50
|
+
1991: 21811.6,
|
|
51
|
+
1992: 22935.42,
|
|
52
|
+
1993: 23132.67,
|
|
53
|
+
1994: 23753.53,
|
|
54
|
+
1995: 24705.66,
|
|
55
|
+
1996: 25913.9,
|
|
56
|
+
1997: 27426.0,
|
|
57
|
+
1998: 28861.44,
|
|
58
|
+
1999: 30469.84,
|
|
59
|
+
2000: 32154.82,
|
|
60
|
+
2001: 32921.92,
|
|
61
|
+
2002: 33252.09,
|
|
62
|
+
2003: 34064.95,
|
|
63
|
+
2004: 35648.55,
|
|
64
|
+
2005: 36952.94,
|
|
65
|
+
2006: 38651.41,
|
|
66
|
+
2007: 40405.48,
|
|
67
|
+
2008: 41334.97,
|
|
68
|
+
2009: 40711.61,
|
|
69
|
+
2010: 41673.83,
|
|
70
|
+
2011: 42979.61,
|
|
71
|
+
2012: 44321.67,
|
|
72
|
+
2013: 44888.16,
|
|
73
|
+
2014: 46481.52,
|
|
74
|
+
2015: 48098.63,
|
|
75
|
+
2016: 48642.15,
|
|
76
|
+
2017: 50321.89,
|
|
77
|
+
2018: 52145.8,
|
|
78
|
+
2019: 54099.99,
|
|
79
|
+
2020: 55628.6,
|
|
80
|
+
2021: 60575.07,
|
|
81
|
+
2022: 63795.13,
|
|
82
|
+
2023: 66621.8,
|
|
83
|
+
2024: 69846.57,
|
|
84
|
+
};
|
|
85
|
+
/** PIA formula bend points by year of eligibility (attain age 62 / disability / death). */
|
|
86
|
+
export const PIA_BEND_POINTS = {
|
|
87
|
+
1979: { first: 180, second: 1085 },
|
|
88
|
+
1980: { first: 194, second: 1171 },
|
|
89
|
+
1981: { first: 211, second: 1274 },
|
|
90
|
+
1982: { first: 230, second: 1388 },
|
|
91
|
+
1983: { first: 254, second: 1528 },
|
|
92
|
+
1984: { first: 267, second: 1612 },
|
|
93
|
+
1985: { first: 280, second: 1691 },
|
|
94
|
+
1986: { first: 297, second: 1790 },
|
|
95
|
+
1987: { first: 310, second: 1866 },
|
|
96
|
+
1988: { first: 319, second: 1922 },
|
|
97
|
+
1989: { first: 339, second: 2044 },
|
|
98
|
+
1990: { first: 356, second: 2145 },
|
|
99
|
+
1991: { first: 370, second: 2230 },
|
|
100
|
+
1992: { first: 387, second: 2333 },
|
|
101
|
+
1993: { first: 401, second: 2420 },
|
|
102
|
+
1994: { first: 422, second: 2545 },
|
|
103
|
+
1995: { first: 426, second: 2567 },
|
|
104
|
+
1996: { first: 437, second: 2635 },
|
|
105
|
+
1997: { first: 455, second: 2741 },
|
|
106
|
+
1998: { first: 477, second: 2875 },
|
|
107
|
+
1999: { first: 505, second: 3043 },
|
|
108
|
+
2000: { first: 531, second: 3202 },
|
|
109
|
+
2001: { first: 561, second: 3381 },
|
|
110
|
+
2002: { first: 592, second: 3567 },
|
|
111
|
+
2003: { first: 606, second: 3653 },
|
|
112
|
+
2004: { first: 612, second: 3689 },
|
|
113
|
+
2005: { first: 627, second: 3779 },
|
|
114
|
+
2006: { first: 656, second: 3955 },
|
|
115
|
+
2007: { first: 680, second: 4100 },
|
|
116
|
+
2008: { first: 711, second: 4288 },
|
|
117
|
+
2009: { first: 744, second: 4483 },
|
|
118
|
+
2010: { first: 761, second: 4586 },
|
|
119
|
+
2011: { first: 749, second: 4517 },
|
|
120
|
+
2012: { first: 767, second: 4624 },
|
|
121
|
+
2013: { first: 791, second: 4768 },
|
|
122
|
+
2014: { first: 816, second: 4917 },
|
|
123
|
+
2015: { first: 826, second: 4980 },
|
|
124
|
+
2016: { first: 856, second: 5157 },
|
|
125
|
+
2017: { first: 885, second: 5336 },
|
|
126
|
+
2018: { first: 895, second: 5397 },
|
|
127
|
+
2019: { first: 926, second: 5583 },
|
|
128
|
+
2020: { first: 960, second: 5785 },
|
|
129
|
+
2021: { first: 996, second: 6002 },
|
|
130
|
+
2022: { first: 1024, second: 6172 },
|
|
131
|
+
2023: { first: 1115, second: 6721 },
|
|
132
|
+
2024: { first: 1174, second: 7078 },
|
|
133
|
+
2025: { first: 1226, second: 7391 },
|
|
134
|
+
2026: { first: 1286, second: 7749 },
|
|
135
|
+
};
|
|
136
|
+
/** Retirement/survivor family-maximum formula bend points by year of eligibility. */
|
|
137
|
+
export const FAMILY_MAXIMUM_BEND_POINTS = {
|
|
138
|
+
1979: { first: 230, second: 332, third: 433 },
|
|
139
|
+
1980: { first: 248, second: 358, third: 467 },
|
|
140
|
+
1981: { first: 270, second: 390, third: 508 },
|
|
141
|
+
1982: { first: 294, second: 425, third: 554 },
|
|
142
|
+
1983: { first: 324, second: 468, third: 610 },
|
|
143
|
+
1984: { first: 342, second: 493, third: 643 },
|
|
144
|
+
1985: { first: 358, second: 517, third: 675 },
|
|
145
|
+
1986: { first: 379, second: 548, third: 714 },
|
|
146
|
+
1987: { first: 396, second: 571, third: 745 },
|
|
147
|
+
1988: { first: 407, second: 588, third: 767 },
|
|
148
|
+
1989: { first: 433, second: 626, third: 816 },
|
|
149
|
+
1990: { first: 455, second: 656, third: 856 },
|
|
150
|
+
1991: { first: 473, second: 682, third: 890 },
|
|
151
|
+
1992: { first: 495, second: 714, third: 931 },
|
|
152
|
+
1993: { first: 513, second: 740, third: 966 },
|
|
153
|
+
1994: { first: 539, second: 779, third: 1016 },
|
|
154
|
+
1995: { first: 544, second: 785, third: 1024 },
|
|
155
|
+
1996: { first: 559, second: 806, third: 1052 },
|
|
156
|
+
1997: { first: 581, second: 839, third: 1094 },
|
|
157
|
+
1998: { first: 609, second: 880, third: 1147 },
|
|
158
|
+
1999: { first: 645, second: 931, third: 1214 },
|
|
159
|
+
2000: { first: 679, second: 980, third: 1278 },
|
|
160
|
+
2001: { first: 717, second: 1034, third: 1349 },
|
|
161
|
+
2002: { first: 756, second: 1092, third: 1424 },
|
|
162
|
+
2003: { first: 774, second: 1118, third: 1458 },
|
|
163
|
+
2004: { first: 782, second: 1129, third: 1472 },
|
|
164
|
+
2005: { first: 801, second: 1156, third: 1508 },
|
|
165
|
+
2006: { first: 838, second: 1210, third: 1578 },
|
|
166
|
+
2007: { first: 869, second: 1255, third: 1636 },
|
|
167
|
+
2008: { first: 909, second: 1312, third: 1711 },
|
|
168
|
+
2009: { first: 950, second: 1372, third: 1789 },
|
|
169
|
+
2010: { first: 972, second: 1403, third: 1830 },
|
|
170
|
+
2011: { first: 957, second: 1382, third: 1803 },
|
|
171
|
+
2012: { first: 980, second: 1415, third: 1845 },
|
|
172
|
+
2013: { first: 1011, second: 1459, third: 1903 },
|
|
173
|
+
2014: { first: 1042, second: 1505, third: 1962 },
|
|
174
|
+
2015: { first: 1056, second: 1524, third: 1987 },
|
|
175
|
+
2016: { first: 1093, second: 1578, third: 2058 },
|
|
176
|
+
2017: { first: 1131, second: 1633, third: 2130 },
|
|
177
|
+
2018: { first: 1144, second: 1651, third: 2154 },
|
|
178
|
+
2019: { first: 1184, second: 1708, third: 2228 },
|
|
179
|
+
2020: { first: 1226, second: 1770, third: 2309 },
|
|
180
|
+
2021: { first: 1272, second: 1837, third: 2395 },
|
|
181
|
+
2022: { first: 1308, second: 1889, third: 2463 },
|
|
182
|
+
2023: { first: 1425, second: 2056, third: 2682 },
|
|
183
|
+
2024: { first: 1500, second: 2166, third: 2825 },
|
|
184
|
+
2025: { first: 1567, second: 2262, third: 2950 },
|
|
185
|
+
2026: { first: 1643, second: 2371, third: 3093 },
|
|
186
|
+
};
|
|
187
|
+
/** OASDI contribution & benefit base (taxable maximum), dollars. */
|
|
188
|
+
export const WAGE_BASE_BY_YEAR = {
|
|
189
|
+
1979: 22900,
|
|
190
|
+
1980: 25900,
|
|
191
|
+
1981: 29700,
|
|
192
|
+
1982: 32400,
|
|
193
|
+
1983: 35700,
|
|
194
|
+
1984: 37800,
|
|
195
|
+
1985: 39600,
|
|
196
|
+
1986: 42000,
|
|
197
|
+
1987: 43800,
|
|
198
|
+
1988: 45000,
|
|
199
|
+
1989: 48000,
|
|
200
|
+
1990: 51300,
|
|
201
|
+
1991: 53400,
|
|
202
|
+
1992: 55500,
|
|
203
|
+
1993: 57600,
|
|
204
|
+
1994: 60600,
|
|
205
|
+
1995: 61200,
|
|
206
|
+
1996: 62700,
|
|
207
|
+
1997: 65400,
|
|
208
|
+
1998: 68400,
|
|
209
|
+
1999: 72600,
|
|
210
|
+
2000: 76200,
|
|
211
|
+
2001: 80400,
|
|
212
|
+
2002: 84900,
|
|
213
|
+
2003: 87000,
|
|
214
|
+
2004: 87900,
|
|
215
|
+
2005: 90000,
|
|
216
|
+
2006: 94200,
|
|
217
|
+
2007: 97500,
|
|
218
|
+
2008: 102000,
|
|
219
|
+
2009: 106800,
|
|
220
|
+
2010: 106800,
|
|
221
|
+
2011: 106800,
|
|
222
|
+
2012: 110100,
|
|
223
|
+
2013: 113700,
|
|
224
|
+
2014: 117000,
|
|
225
|
+
2015: 118500,
|
|
226
|
+
2016: 118500,
|
|
227
|
+
2017: 127200,
|
|
228
|
+
2018: 128400,
|
|
229
|
+
2019: 132900,
|
|
230
|
+
2020: 137700,
|
|
231
|
+
2021: 142800,
|
|
232
|
+
2022: 147000,
|
|
233
|
+
2023: 160200,
|
|
234
|
+
2024: 168600,
|
|
235
|
+
2025: 176100,
|
|
236
|
+
2026: 184500,
|
|
237
|
+
};
|
|
238
|
+
function maxTableYear(table) {
|
|
239
|
+
let m = -Infinity;
|
|
240
|
+
for (const k of Object.keys(table)) {
|
|
241
|
+
const y = Number(k);
|
|
242
|
+
if (Number.isFinite(y) && y > m)
|
|
243
|
+
m = y;
|
|
244
|
+
}
|
|
245
|
+
return m;
|
|
246
|
+
}
|
|
247
|
+
/** Latest calendar year for which `AWI_BY_YEAR` has an official value. */
|
|
248
|
+
export const LATEST_PUBLISHED_AWI_YEAR = maxTableYear(AWI_BY_YEAR);
|
|
249
|
+
/** Latest eligibility year for which `PIA_BEND_POINTS` has an official pair. */
|
|
250
|
+
export const LATEST_PIA_BEND_POINT_ELIGIBILITY_YEAR = maxTableYear(PIA_BEND_POINTS);
|
|
251
|
+
/** Latest eligibility year for which `FAMILY_MAXIMUM_BEND_POINTS` has an official triplet. */
|
|
252
|
+
export const LATEST_FAMILY_MAXIMUM_BEND_POINT_ELIGIBILITY_YEAR = maxTableYear(FAMILY_MAXIMUM_BEND_POINTS);
|
|
253
|
+
/** Latest calendar year for which `WAGE_BASE_BY_YEAR` has an official taxable maximum. */
|
|
254
|
+
export const LATEST_PUBLISHED_WAGE_BASE_YEAR = maxTableYear(WAGE_BASE_BY_YEAR);
|
|
255
|
+
export function awiForYear(year) {
|
|
256
|
+
return AWI_BY_YEAR[year];
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Published AWI, or the latest on file when the year is not yet released (illustrative only).
|
|
260
|
+
*/
|
|
261
|
+
export function awiForYearOrLatest(year) {
|
|
262
|
+
const v = AWI_BY_YEAR[year];
|
|
263
|
+
if (v !== undefined && v > 0)
|
|
264
|
+
return v;
|
|
265
|
+
return AWI_BY_YEAR[LATEST_PUBLISHED_AWI_YEAR];
|
|
266
|
+
}
|
|
267
|
+
export function bendPointsForEligibilityYear(year) {
|
|
268
|
+
return PIA_BEND_POINTS[year];
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Official bend points for that eligibility year, or the latest on file when SSA has not yet
|
|
272
|
+
* published figures (illustrative only).
|
|
273
|
+
*/
|
|
274
|
+
export function bendPointsForEligibilityYearOrLatest(eligibilityYear) {
|
|
275
|
+
const direct = PIA_BEND_POINTS[eligibilityYear];
|
|
276
|
+
if (direct)
|
|
277
|
+
return direct;
|
|
278
|
+
return PIA_BEND_POINTS[LATEST_PIA_BEND_POINT_ELIGIBILITY_YEAR];
|
|
279
|
+
}
|
|
280
|
+
export function familyMaximumBendPointsForEligibilityYear(year) {
|
|
281
|
+
return FAMILY_MAXIMUM_BEND_POINTS[year];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Official family-maximum bend points for that eligibility year, or the latest
|
|
285
|
+
* on file when SSA has not yet published figures (illustrative only).
|
|
286
|
+
*/
|
|
287
|
+
export function familyMaximumBendPointsForEligibilityYearOrLatest(eligibilityYear) {
|
|
288
|
+
const direct = FAMILY_MAXIMUM_BEND_POINTS[eligibilityYear];
|
|
289
|
+
if (direct)
|
|
290
|
+
return direct;
|
|
291
|
+
return FAMILY_MAXIMUM_BEND_POINTS[LATEST_FAMILY_MAXIMUM_BEND_POINT_ELIGIBILITY_YEAR];
|
|
292
|
+
}
|
|
293
|
+
export function wageBaseForYear(year) {
|
|
294
|
+
return WAGE_BASE_BY_YEAR[year];
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Social Security taxable maximum (wage base) for a year, or the latest on file
|
|
298
|
+
* when SSA has not yet published it (illustrative only). Used to cap projected
|
|
299
|
+
* future earnings so they cannot exceed the taxable maximum.
|
|
300
|
+
*/
|
|
301
|
+
export function wageBaseForYearOrLatest(year) {
|
|
302
|
+
const v = WAGE_BASE_BY_YEAR[year];
|
|
303
|
+
if (v !== undefined && v > 0)
|
|
304
|
+
return v;
|
|
305
|
+
return WAGE_BASE_BY_YEAR[LATEST_PUBLISHED_WAGE_BASE_YEAR];
|
|
306
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Survivor (widow(er)) benefit — the shared, SSA-cited computation used by
|
|
3
|
+
* **both** the actuarial PV view (`survivorSwitching.ts`) and the projection
|
|
4
|
+
* ledger (`simulate.ts` survivor step-up + `maritalBenefits.ts` former-spouse
|
|
5
|
+
* path), so the two can't drift (the gap-analysis row this closes was exactly
|
|
6
|
+
* that drift: the PV view had a reduction, the ledger didn't).
|
|
7
|
+
*
|
|
8
|
+
* Rules encoded (see DOCS/domain/domain-rules-reference.md §4, each cited):
|
|
9
|
+
* - **Survivor base = the deceased's actual (claim-age-adjusted) benefit**,
|
|
10
|
+
* including the deceased's delayed-retirement credits when the deceased
|
|
11
|
+
* delayed past FRA (not flat PIA).
|
|
12
|
+
* - **RIB-LIM ("widow's limit")**: when the deceased claimed reduced benefits
|
|
13
|
+
* early, the survivor is capped at the larger of the deceased's actual
|
|
14
|
+
* reduced benefit or 82.5% of the deceased's PIA — equivalently
|
|
15
|
+
* `max(deceasedActual, 0.825 × PIA)`, which both floors the survivor when the
|
|
16
|
+
* deceased claimed early and passes delayed credits through when the deceased
|
|
17
|
+
* delayed.
|
|
18
|
+
* - **Early-claim widow(er) reduction**: claiming survivor between 60 and the
|
|
19
|
+
* survivor's FRA reduces the benefit by up to 28.5% at 60 (linear monthly
|
|
20
|
+
* proration) — a floor of 71.5% of the survivor base.
|
|
21
|
+
*
|
|
22
|
+
* Inputs are monthly, today's dollars, pre-COLA/haircut; callers scale to the
|
|
23
|
+
* annual COLA-adjusted frame they need. Illustrative, not a filing tool.
|
|
24
|
+
*/
|
|
25
|
+
import type { ClaimAge } from './claimFactor.js';
|
|
26
|
+
/** Earliest age a (non-disabled) widow(er) can claim survivor benefits. */
|
|
27
|
+
export declare const SURVIVOR_EARLIEST_AGE = 60;
|
|
28
|
+
/** Maximum widow(er) reduction, applied at the earliest claim age (28.5% → 71.5% payable). */
|
|
29
|
+
export declare const SURVIVOR_MAX_REDUCTION = 0.285;
|
|
30
|
+
/** RIB-LIM: the widow's-limit floor as a fraction of the deceased's PIA. */
|
|
31
|
+
export declare const WIDOW_LIMIT_PIA_FRACTION = 0.825;
|
|
32
|
+
export interface SurvivorBenefitInput {
|
|
33
|
+
/** The deceased worker's PIA (today's dollars, pre-COLA/haircut). */
|
|
34
|
+
deceasedPiaMonthly: number;
|
|
35
|
+
/**
|
|
36
|
+
* The deceased's actual monthly benefit = PIA × the deceased's claim factor
|
|
37
|
+
* (claim-age-adjusted, including delayed-retirement credits). Pre-COLA/haircut.
|
|
38
|
+
*/
|
|
39
|
+
deceasedActualMonthly: number;
|
|
40
|
+
/** The survivor's claim age (years + months, ≥60) — months are honored. */
|
|
41
|
+
survivorClaimAge: ClaimAge;
|
|
42
|
+
/** Survivor (widow(er)) FRA in total months — see `survivorFraForBirthYear`. */
|
|
43
|
+
survivorFraMonths: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Widow(er) reduction factor for claiming survivor at `ageMonths` total months
|
|
47
|
+
* (linear from a 28.5% reduction at age 60 to no reduction at the survivor's
|
|
48
|
+
* FRA). 1.0 at/after FRA; 0.715 at/before 60. Accepts total months so a survivor
|
|
49
|
+
* claiming at exactly their survivor FRA (e.g. 66y8m for born 1960+) is not
|
|
50
|
+
* reduced.
|
|
51
|
+
*/
|
|
52
|
+
export declare function survivorReductionFactor(ageMonths: number, survivorFraMonths: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* The survivor monthly benefit payable (today's dollars, pre-COLA/haircut):
|
|
55
|
+
* `max(deceasedActual, 82.5% × PIA)` (RIB-LIM) × the widow(er) reduction factor.
|
|
56
|
+
* The survivor's claim-age **months** are carried through (a survivor at exactly
|
|
57
|
+
* their survivor FRA is unreduced). Returns 0 when the deceased had no PIA.
|
|
58
|
+
*/
|
|
59
|
+
export declare function survivorBenefitMonthly(input: SurvivorBenefitInput): number;
|
|
60
|
+
/** Convenience: a deceased claim age of "at/after FRA" (no early reduction, no DRCs). */
|
|
61
|
+
export declare const DECEASED_CLAIMED_AT_FRA: ClaimAge;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Survivor (widow(er)) benefit — the shared, SSA-cited computation used by
|
|
3
|
+
* **both** the actuarial PV view (`survivorSwitching.ts`) and the projection
|
|
4
|
+
* ledger (`simulate.ts` survivor step-up + `maritalBenefits.ts` former-spouse
|
|
5
|
+
* path), so the two can't drift (the gap-analysis row this closes was exactly
|
|
6
|
+
* that drift: the PV view had a reduction, the ledger didn't).
|
|
7
|
+
*
|
|
8
|
+
* Rules encoded (see DOCS/domain/domain-rules-reference.md §4, each cited):
|
|
9
|
+
* - **Survivor base = the deceased's actual (claim-age-adjusted) benefit**,
|
|
10
|
+
* including the deceased's delayed-retirement credits when the deceased
|
|
11
|
+
* delayed past FRA (not flat PIA).
|
|
12
|
+
* - **RIB-LIM ("widow's limit")**: when the deceased claimed reduced benefits
|
|
13
|
+
* early, the survivor is capped at the larger of the deceased's actual
|
|
14
|
+
* reduced benefit or 82.5% of the deceased's PIA — equivalently
|
|
15
|
+
* `max(deceasedActual, 0.825 × PIA)`, which both floors the survivor when the
|
|
16
|
+
* deceased claimed early and passes delayed credits through when the deceased
|
|
17
|
+
* delayed.
|
|
18
|
+
* - **Early-claim widow(er) reduction**: claiming survivor between 60 and the
|
|
19
|
+
* survivor's FRA reduces the benefit by up to 28.5% at 60 (linear monthly
|
|
20
|
+
* proration) — a floor of 71.5% of the survivor base.
|
|
21
|
+
*
|
|
22
|
+
* Inputs are monthly, today's dollars, pre-COLA/haircut; callers scale to the
|
|
23
|
+
* annual COLA-adjusted frame they need. Illustrative, not a filing tool.
|
|
24
|
+
*/
|
|
25
|
+
/** Earliest age a (non-disabled) widow(er) can claim survivor benefits. */
|
|
26
|
+
export const SURVIVOR_EARLIEST_AGE = 60;
|
|
27
|
+
/** Maximum widow(er) reduction, applied at the earliest claim age (28.5% → 71.5% payable). */
|
|
28
|
+
export const SURVIVOR_MAX_REDUCTION = 0.285;
|
|
29
|
+
/** RIB-LIM: the widow's-limit floor as a fraction of the deceased's PIA. */
|
|
30
|
+
export const WIDOW_LIMIT_PIA_FRACTION = 0.825;
|
|
31
|
+
/**
|
|
32
|
+
* Widow(er) reduction factor for claiming survivor at `ageMonths` total months
|
|
33
|
+
* (linear from a 28.5% reduction at age 60 to no reduction at the survivor's
|
|
34
|
+
* FRA). 1.0 at/after FRA; 0.715 at/before 60. Accepts total months so a survivor
|
|
35
|
+
* claiming at exactly their survivor FRA (e.g. 66y8m for born 1960+) is not
|
|
36
|
+
* reduced.
|
|
37
|
+
*/
|
|
38
|
+
export function survivorReductionFactor(ageMonths, survivorFraMonths) {
|
|
39
|
+
if (ageMonths >= survivorFraMonths)
|
|
40
|
+
return 1;
|
|
41
|
+
const earliest = SURVIVOR_EARLIEST_AGE * 12;
|
|
42
|
+
if (ageMonths <= earliest)
|
|
43
|
+
return 1 - SURVIVOR_MAX_REDUCTION;
|
|
44
|
+
const frac = (ageMonths - earliest) / (survivorFraMonths - earliest);
|
|
45
|
+
return 1 - SURVIVOR_MAX_REDUCTION * (1 - frac);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The survivor monthly benefit payable (today's dollars, pre-COLA/haircut):
|
|
49
|
+
* `max(deceasedActual, 82.5% × PIA)` (RIB-LIM) × the widow(er) reduction factor.
|
|
50
|
+
* The survivor's claim-age **months** are carried through (a survivor at exactly
|
|
51
|
+
* their survivor FRA is unreduced). Returns 0 when the deceased had no PIA.
|
|
52
|
+
*/
|
|
53
|
+
export function survivorBenefitMonthly(input) {
|
|
54
|
+
if (input.deceasedPiaMonthly <= 0)
|
|
55
|
+
return 0;
|
|
56
|
+
const base = Math.max(input.deceasedActualMonthly, WIDOW_LIMIT_PIA_FRACTION * input.deceasedPiaMonthly);
|
|
57
|
+
const ageMonths = input.survivorClaimAge.years * 12 + input.survivorClaimAge.months;
|
|
58
|
+
return base * survivorReductionFactor(ageMonths, input.survivorFraMonths);
|
|
59
|
+
}
|
|
60
|
+
/** Convenience: a deceased claim age of "at/after FRA" (no early reduction, no DRCs). */
|
|
61
|
+
export const DECEASED_CLAIMED_AT_FRA = { years: 67, months: 0 };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amortization-based withdrawal (ABW) — spending-paths & SWR-lenses plan, Goal 2.
|
|
3
|
+
*
|
|
4
|
+
* The rule the Bogleheads wiki formalized and that VPW, TPAW, and CAPE-based
|
|
5
|
+
* rules are parameterizations of: every year, re-amortize the *actual*
|
|
6
|
+
* start-of-year portfolio over the remaining horizon at an expected return,
|
|
7
|
+
* and spend this year's payment. Because the payment is recomputed from the
|
|
8
|
+
* real balance annually, the rule self-corrects after market surprises and —
|
|
9
|
+
* by the amortization identity — depletes exactly at the horizon when realized
|
|
10
|
+
* returns equal expected returns.
|
|
11
|
+
*
|
|
12
|
+
* Timing convention matches the ledger (simulate.ts): expenses are funded from
|
|
13
|
+
* the start-of-year balance and growth applies to what remains at year end —
|
|
14
|
+
* an annuity-due. With payments planned to grow at a real tilt g and an
|
|
15
|
+
* expected real return r, the payment ratio x = (1+g)/(1+r) is the same in
|
|
16
|
+
* real and nominal terms (inflation cancels), so the payment can be computed
|
|
17
|
+
* directly on the nominal start-of-year balance:
|
|
18
|
+
*
|
|
19
|
+
* payment = balance × (1 − x) / (1 − x^n) (x ≠ 1), or balance / n (x = 1)
|
|
20
|
+
*
|
|
21
|
+
* where n is the remaining years including the current one. n = 1 spends the
|
|
22
|
+
* whole remaining balance — the exact-depletion terminal case.
|
|
23
|
+
*
|
|
24
|
+
* These functions are pure; the ledger owns per-year state and funds the
|
|
25
|
+
* payment through the same tax/withdrawal cascade as every other expense.
|
|
26
|
+
*/
|
|
27
|
+
import type { AbwPolicy } from '../model/plan.js';
|
|
28
|
+
/** Defaults documented in DOCS/domain/domain-rules-reference.md §14 ("Spending paths & SWR lenses"). */
|
|
29
|
+
export declare const ABW_DEFAULTS: {
|
|
30
|
+
/**
|
|
31
|
+
* VPW-style fixed expected real return: the Bogleheads VPW table's global
|
|
32
|
+
* internal rates of return (stocks 5.0%/yr real, bonds 1.9%/yr real)
|
|
33
|
+
* weighted 60/40 ⇒ 3.8%/yr real.
|
|
34
|
+
*/
|
|
35
|
+
fixedRealReturnPct: number;
|
|
36
|
+
/** Matches the CAPE-conditioned market model's default starting CAPE. */
|
|
37
|
+
startingCape: number;
|
|
38
|
+
equitySharePct: number;
|
|
39
|
+
/** Long-run TIPS real yield near the mid-2026 curve (~2%/yr real). */
|
|
40
|
+
bondRealYieldPct: number;
|
|
41
|
+
tiltPct: number;
|
|
42
|
+
horizon: "planningAge";
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Expected real return %/yr implied by the policy's return source:
|
|
46
|
+
* fixed as entered; CAPE = 100/CAPE (the cyclically-adjusted earnings yield)
|
|
47
|
+
* blended with the bond real yield at the equity share; TIPS = the bond real
|
|
48
|
+
* yield alone.
|
|
49
|
+
*/
|
|
50
|
+
export declare function abwExpectedRealReturnPct(abw: AbwPolicy | undefined): number;
|
|
51
|
+
/**
|
|
52
|
+
* This year's amortized payment from a start-of-year balance over
|
|
53
|
+
* `remainingYears` (current year inclusive) at `realReturnPct` expected real
|
|
54
|
+
* return, with payments planned to grow `tiltPct`%/yr real. Balance ≤ 0 or a
|
|
55
|
+
* non-finite input pays 0; remainingYears ≤ 1 pays the whole balance.
|
|
56
|
+
*/
|
|
57
|
+
export declare function abwAnnualPayment(balance: number, realReturnPct: number, tiltPct: number, remainingYears: number): number;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amortization-based withdrawal (ABW) — spending-paths & SWR-lenses plan, Goal 2.
|
|
3
|
+
*
|
|
4
|
+
* The rule the Bogleheads wiki formalized and that VPW, TPAW, and CAPE-based
|
|
5
|
+
* rules are parameterizations of: every year, re-amortize the *actual*
|
|
6
|
+
* start-of-year portfolio over the remaining horizon at an expected return,
|
|
7
|
+
* and spend this year's payment. Because the payment is recomputed from the
|
|
8
|
+
* real balance annually, the rule self-corrects after market surprises and —
|
|
9
|
+
* by the amortization identity — depletes exactly at the horizon when realized
|
|
10
|
+
* returns equal expected returns.
|
|
11
|
+
*
|
|
12
|
+
* Timing convention matches the ledger (simulate.ts): expenses are funded from
|
|
13
|
+
* the start-of-year balance and growth applies to what remains at year end —
|
|
14
|
+
* an annuity-due. With payments planned to grow at a real tilt g and an
|
|
15
|
+
* expected real return r, the payment ratio x = (1+g)/(1+r) is the same in
|
|
16
|
+
* real and nominal terms (inflation cancels), so the payment can be computed
|
|
17
|
+
* directly on the nominal start-of-year balance:
|
|
18
|
+
*
|
|
19
|
+
* payment = balance × (1 − x) / (1 − x^n) (x ≠ 1), or balance / n (x = 1)
|
|
20
|
+
*
|
|
21
|
+
* where n is the remaining years including the current one. n = 1 spends the
|
|
22
|
+
* whole remaining balance — the exact-depletion terminal case.
|
|
23
|
+
*
|
|
24
|
+
* These functions are pure; the ledger owns per-year state and funds the
|
|
25
|
+
* payment through the same tax/withdrawal cascade as every other expense.
|
|
26
|
+
*/
|
|
27
|
+
/** Defaults documented in DOCS/domain/domain-rules-reference.md §14 ("Spending paths & SWR lenses"). */
|
|
28
|
+
export const ABW_DEFAULTS = {
|
|
29
|
+
/**
|
|
30
|
+
* VPW-style fixed expected real return: the Bogleheads VPW table's global
|
|
31
|
+
* internal rates of return (stocks 5.0%/yr real, bonds 1.9%/yr real)
|
|
32
|
+
* weighted 60/40 ⇒ 3.8%/yr real.
|
|
33
|
+
*/
|
|
34
|
+
fixedRealReturnPct: 3.8,
|
|
35
|
+
/** Matches the CAPE-conditioned market model's default starting CAPE. */
|
|
36
|
+
startingCape: 25,
|
|
37
|
+
equitySharePct: 60,
|
|
38
|
+
/** Long-run TIPS real yield near the mid-2026 curve (~2%/yr real). */
|
|
39
|
+
bondRealYieldPct: 2.0,
|
|
40
|
+
tiltPct: 0,
|
|
41
|
+
horizon: 'planningAge',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Expected real return %/yr implied by the policy's return source:
|
|
45
|
+
* fixed as entered; CAPE = 100/CAPE (the cyclically-adjusted earnings yield)
|
|
46
|
+
* blended with the bond real yield at the equity share; TIPS = the bond real
|
|
47
|
+
* yield alone.
|
|
48
|
+
*/
|
|
49
|
+
export function abwExpectedRealReturnPct(abw) {
|
|
50
|
+
const source = abw?.returnSource ?? 'fixed';
|
|
51
|
+
const bond = abw?.bondRealYieldPct ?? ABW_DEFAULTS.bondRealYieldPct;
|
|
52
|
+
if (source === 'tips')
|
|
53
|
+
return bond;
|
|
54
|
+
if (source === 'cape') {
|
|
55
|
+
const cape = abw?.startingCape ?? ABW_DEFAULTS.startingCape;
|
|
56
|
+
const equityShare = (abw?.equitySharePct ?? ABW_DEFAULTS.equitySharePct) / 100;
|
|
57
|
+
const caey = 100 / cape;
|
|
58
|
+
return caey * equityShare + bond * (1 - equityShare);
|
|
59
|
+
}
|
|
60
|
+
return abw?.fixedRealReturnPct ?? ABW_DEFAULTS.fixedRealReturnPct;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* This year's amortized payment from a start-of-year balance over
|
|
64
|
+
* `remainingYears` (current year inclusive) at `realReturnPct` expected real
|
|
65
|
+
* return, with payments planned to grow `tiltPct`%/yr real. Balance ≤ 0 or a
|
|
66
|
+
* non-finite input pays 0; remainingYears ≤ 1 pays the whole balance.
|
|
67
|
+
*/
|
|
68
|
+
export function abwAnnualPayment(balance, realReturnPct, tiltPct, remainingYears) {
|
|
69
|
+
if (!Number.isFinite(balance) || balance <= 0)
|
|
70
|
+
return 0;
|
|
71
|
+
const n = Math.floor(remainingYears);
|
|
72
|
+
if (n <= 1)
|
|
73
|
+
return balance;
|
|
74
|
+
const x = (1 + tiltPct / 100) / (1 + realReturnPct / 100);
|
|
75
|
+
if (!Number.isFinite(x) || x <= 0)
|
|
76
|
+
return balance / n;
|
|
77
|
+
if (Math.abs(x - 1) < 1e-9)
|
|
78
|
+
return balance / n;
|
|
79
|
+
return (balance * (1 - x)) / (1 - Math.pow(x, n));
|
|
80
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flexible one-time-goal scheduler. One scheduler instance belongs to one
|
|
3
|
+
* projection path, so goal resolution is stateful and deterministic: a goal can
|
|
4
|
+
* be funded, partially funded, or skipped at most once.
|
|
5
|
+
*/
|
|
6
|
+
import type { GoalFlexibility, OneTimeGoal, SpendingClassification } from '../model/plan.js';
|
|
7
|
+
export interface SchedulableGoal {
|
|
8
|
+
id: string;
|
|
9
|
+
classification: SpendingClassification;
|
|
10
|
+
flexibility: GoalFlexibility;
|
|
11
|
+
earliestYear: number;
|
|
12
|
+
targetYear: number;
|
|
13
|
+
latestYear: number;
|
|
14
|
+
priority: number;
|
|
15
|
+
order: number;
|
|
16
|
+
amountTodayDollars: number;
|
|
17
|
+
minFundingPct: number;
|
|
18
|
+
allowPartialFunding: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve a stored goal's optional flexibility fields to their defaults. */
|
|
21
|
+
export declare function toSchedulableGoal(goal: OneTimeGoal, index: number): SchedulableGoal;
|
|
22
|
+
export type GoalOutcome = 'funded' | 'partiallyFunded' | 'deferred' | 'skipped';
|
|
23
|
+
export interface GoalYearResult {
|
|
24
|
+
id: string;
|
|
25
|
+
classification: SpendingClassification;
|
|
26
|
+
outcome: GoalOutcome;
|
|
27
|
+
/** The goal's intended (inflated) cost this year, regardless of outcome. */
|
|
28
|
+
amountNominal: number;
|
|
29
|
+
/** Nominal dollars funded this year. */
|
|
30
|
+
fundedNominal: number;
|
|
31
|
+
/** Nominal dollars not funded when the goal resolves below its intended cost. */
|
|
32
|
+
unfundedNominal: number;
|
|
33
|
+
}
|
|
34
|
+
export interface GoalYearContext {
|
|
35
|
+
/** Cumulative inflation factor from the projection start to this year. */
|
|
36
|
+
inflFactor: number;
|
|
37
|
+
/** True when the guardrail policy is cutting discretionary spending this year. */
|
|
38
|
+
cutting: boolean;
|
|
39
|
+
/** True when strong-path rules may pull goals earlier than their target year. */
|
|
40
|
+
canPullForward?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Hard budget for flexible goals this year. `null` means unlimited; omitted
|
|
43
|
+
* means no flexible-goal budget while cutting and unlimited otherwise.
|
|
44
|
+
*/
|
|
45
|
+
availableBudget?: number | null;
|
|
46
|
+
}
|
|
47
|
+
export interface GoalPlanningResult {
|
|
48
|
+
results: GoalYearResult[];
|
|
49
|
+
remainingBudget: number | null;
|
|
50
|
+
}
|
|
51
|
+
export interface GoalScheduler {
|
|
52
|
+
/** Decide every goal touching `year`; each goal resolves at most once. */
|
|
53
|
+
planYear(year: number, ctx: GoalYearContext): GoalPlanningResult;
|
|
54
|
+
/** True after a goal has been funded, partially funded, or skipped. */
|
|
55
|
+
isResolved(id: string): boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build a scheduler for a plan's goals. Flexible goals are evaluated by
|
|
59
|
+
* classification, then explicit priority, then original list order so a target
|
|
60
|
+
* car replacement cannot be crowded out by a lower-layer ideal remodel.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createGoalScheduler(goals: SchedulableGoal[]): GoalScheduler;
|