social-security-calculator 2.0.0 → 3.0.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/lib/constants.d.ts +1 -0
- package/lib/constants.js +2 -1
- package/lib/index.d.ts +9 -3
- package/lib/index.js +144 -76
- package/lib/model.d.ts +6 -0
- package/package.json +2 -2
package/lib/constants.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const ELAPSED_YEARS_START_AGE = 22;
|
|
|
8
8
|
export declare const BEND_POINT_DIVISOR = 9779.44;
|
|
9
9
|
export declare const FIRST_BEND_POINT_MULTIPLIER = 180;
|
|
10
10
|
export declare const SECOND_BEND_POINT_MULTIPLIER = 1085;
|
|
11
|
+
export declare const FAM_MAX_BASES: number[];
|
|
11
12
|
export declare const CHILD_SURVIVOR_BENEFIT_PERCENTAGE = 0.75;
|
|
12
13
|
export declare const PIA_PERCENTAGES: {
|
|
13
14
|
readonly FIRST_BRACKET: 0.9;
|
package/lib/constants.js
CHANGED
|
@@ -13,6 +13,7 @@ export const ELAPSED_YEARS_START_AGE = 22;
|
|
|
13
13
|
export const BEND_POINT_DIVISOR = 9779.44; // 1977's AWI - used by dividing against current AWI
|
|
14
14
|
export const FIRST_BEND_POINT_MULTIPLIER = 180;
|
|
15
15
|
export const SECOND_BEND_POINT_MULTIPLIER = 1085;
|
|
16
|
+
export const FAM_MAX_BASES = [230, 332, 433];
|
|
16
17
|
export const CHILD_SURVIVOR_BENEFIT_PERCENTAGE = 0.75; // 75% of PIA for child survivor benefits
|
|
17
18
|
export const PIA_PERCENTAGES = {
|
|
18
19
|
FIRST_BRACKET: 0.9,
|
|
@@ -21,6 +22,6 @@ export const PIA_PERCENTAGES = {
|
|
|
21
22
|
};
|
|
22
23
|
export const EARLY_RETIREMENT_REDUCTION = {
|
|
23
24
|
FIRST_MONTHS: 36,
|
|
24
|
-
FIRST_MONTHS_RATE: 5 / 9 * 0.01,
|
|
25
|
+
FIRST_MONTHS_RATE: 5 / 9 * 0.01, // 5/9 of 1%
|
|
25
26
|
ADDITIONAL_MONTHS_RATE: 5 / 12 * 0.01 // 5/12 of 1%
|
|
26
27
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { BenefitCalculationResult, RetirementDates, Earnings } from './model';
|
|
2
2
|
export declare function calc(birthday: Date, retirementDate: Date, earnings: Earnings): BenefitCalculationResult;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function calcRetirement(birthday: Date, retirementDate: Date, earnings: Earnings): Partial<BenefitCalculationResult>;
|
|
4
4
|
export declare function calculateRetirementDates(birthday: Date, retirementDate: Date): RetirementDates;
|
|
5
|
+
export declare function retirementDateAdjustedPayment(dates: RetirementDates, colaAdjustedPIA: number): number;
|
|
5
6
|
export declare function calculatePIA(AIME: number, baseYear?: number): number;
|
|
7
|
+
export declare function calculateAIME(earnings: Earnings, lookbackYears: number, baseYear?: number): number;
|
|
6
8
|
export declare function getLookbackYears(elapsedYears: number): number;
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function getEnglishCommonLawDate(date: Date): Date;
|
|
9
|
+
export declare function getLookbackYearsDisability(elapsedYears: number): number;
|
|
9
10
|
export declare function getFullRetirementMonths(commonLawBirthDate: Date): number;
|
|
11
|
+
/** Compute Family-Max bend points for an eligibility year given AWI_{year-2}. */
|
|
12
|
+
export declare function getFamilyMaxBendPoints(baseYear: number): [number, number, number];
|
|
13
|
+
/** Apply the family-maximum formula to a PIA using already-computed bend points. */
|
|
14
|
+
export declare function calculateFamilyMaximum(pia: number, [bp1, bp2, bp3]: [number, number, number]): number;
|
|
15
|
+
export declare function getEnglishCommonLawDate(date: Date): Date;
|
package/lib/index.js
CHANGED
|
@@ -1,56 +1,84 @@
|
|
|
1
1
|
import { wageIndex } from './wage-index';
|
|
2
|
-
import { EARLY_RETIREMENT_AGE, WAGE_INDEX_CUTOFF, MAX_RETIREMENT_AGE, MAX_DROP_OUT_YEARS, DROP_OUT_YEARS_DIVISOR, BEND_POINT_DIVISOR, FIRST_BEND_POINT_MULTIPLIER, SECOND_BEND_POINT_MULTIPLIER, PIA_PERCENTAGES, EARLY_RETIREMENT_REDUCTION, ELAPSED_YEARS_START_AGE, LOOKBACK_YEARS, CHILD_SURVIVOR_BENEFIT_PERCENTAGE, } from './constants';
|
|
3
|
-
// Main
|
|
2
|
+
import { EARLY_RETIREMENT_AGE, WAGE_INDEX_CUTOFF, MAX_RETIREMENT_AGE, MAX_DROP_OUT_YEARS, DROP_OUT_YEARS_DIVISOR, BEND_POINT_DIVISOR, FIRST_BEND_POINT_MULTIPLIER, SECOND_BEND_POINT_MULTIPLIER, PIA_PERCENTAGES, EARLY_RETIREMENT_REDUCTION, ELAPSED_YEARS_START_AGE, LOOKBACK_YEARS, CHILD_SURVIVOR_BENEFIT_PERCENTAGE, FAM_MAX_BASES } from './constants';
|
|
3
|
+
// Main entry point
|
|
4
4
|
export function calc(birthday, retirementDate, earnings) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const retirementCalc = calcRetirement(birthday, retirementDate, earnings);
|
|
6
|
+
const survivorCalc = calcSurvivor(birthday, retirementDate, earnings);
|
|
7
|
+
retirementCalc.SurvivorBenefits = survivorCalc;
|
|
8
|
+
return retirementCalc;
|
|
9
|
+
}
|
|
10
|
+
// Core retirement calculation
|
|
11
|
+
export function calcRetirement(birthday, retirementDate, earnings) {
|
|
12
|
+
const context = createCalculationContext(birthday, retirementDate, earnings);
|
|
13
|
+
const regularBenefit = calculateBenefitPipeline(earnings, context.yearAge60, getLookbackYears(context.totalYears));
|
|
14
|
+
const disabilityBenefit = calculateBenefitPipeline(earnings, context.yearAge60, getLookbackYearsDisability(context.totalYears));
|
|
15
|
+
const monthlyBenefit = retirementDateAdjustedPayment(context.dates, regularBenefit.colaAdjustedPIA);
|
|
16
|
+
const disabilityPIAFloored = context.retirementDate > context.dates.fullRetirement ? 0 : Math.floor(disabilityBenefit.colaAdjustedPIA);
|
|
17
|
+
return {
|
|
18
|
+
AIME: regularBenefit.aime,
|
|
19
|
+
PIA: regularBenefit.pia,
|
|
20
|
+
NormalMonthlyBenefit: monthlyBenefit,
|
|
21
|
+
DisabilityEarnings: disabilityPIAFloored,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Reusable benefit calculation pipeline
|
|
25
|
+
function calculateBenefitPipeline(earnings, yearAge60, lookbackYears) {
|
|
26
|
+
const aime = calculateAIME(earnings, lookbackYears, yearAge60);
|
|
27
|
+
const pia = calculatePIA(aime, yearAge60);
|
|
28
|
+
const colaAdjustedPIA = calculateCOLAAdjustments(pia, yearAge60 + 2);
|
|
29
|
+
return { aime, pia, colaAdjustedPIA };
|
|
30
|
+
}
|
|
31
|
+
// Survivor benefits calculation
|
|
32
|
+
function calcSurvivor(birthday, retirementDate, earnings) {
|
|
33
|
+
const context = createCalculationContext(birthday, retirementDate, earnings);
|
|
34
|
+
const benefit = calculateBenefitPipeline(earnings, context.yearAge60, getLookbackYears(context.totalYears));
|
|
35
|
+
const survivorPIA = Math.floor(benefit.colaAdjustedPIA * CHILD_SURVIVOR_BENEFIT_PERCENTAGE);
|
|
36
|
+
const effectiveYear = Math.min(context.yearAge60, WAGE_INDEX_CUTOFF);
|
|
37
|
+
const wageIndexEntry = getWageIndexEntry(effectiveYear);
|
|
38
|
+
const wageIndexLastYear = wageIndexEntry.awi;
|
|
39
|
+
const familyMax = calculateFamilyMaximum(benefit.pia, getFamilyMaxBendPoints(wageIndexLastYear));
|
|
40
|
+
const colaAdjustedFamMax = calculateCOLAAdjustments(familyMax, context.yearAge60 + 2);
|
|
41
|
+
return {
|
|
42
|
+
survivingChild: survivorPIA,
|
|
43
|
+
careGivingSpouse: survivorPIA,
|
|
44
|
+
normalRetirementSpouse: Math.floor(benefit.colaAdjustedPIA),
|
|
45
|
+
familyMaximum: colaAdjustedFamMax
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Create shared calculation context to avoid duplication
|
|
49
|
+
function createCalculationContext(birthday, retirementDate, earnings) {
|
|
50
|
+
validateInputs(birthday, retirementDate, earnings);
|
|
9
51
|
const dates = calculateRetirementDates(birthday, retirementDate);
|
|
10
|
-
if (!earnings || earnings.length === 0) {
|
|
11
|
-
throw new Error('Earnings history cannot be empty');
|
|
12
|
-
}
|
|
13
|
-
if (retirementDate < birthday) {
|
|
14
|
-
throw new Error('Retirement date cannot be before birthday');
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* An individual's earnings are always indexed to the average wage level two years prior to the year of first eligibility.
|
|
18
|
-
* Thus, for a person retiring at age 62 in 2025, the person's earnings would be indexed to the average wage index for 2023.
|
|
19
|
-
*/
|
|
20
52
|
const yearAge60 = birthday.getFullYear() + 60;
|
|
21
53
|
const yearStartCounting = birthday.getFullYear() + ELAPSED_YEARS_START_AGE - 1;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
console.log(`Survivor PIA: ${survivorPIA}`);
|
|
27
|
-
console.log(`Primary Insurance Amount: ${retirementDateAdjustedPayment(dates, survivorPIA)}`);
|
|
28
|
-
const colaAdjustedPIA = calculateCOLAAdjustments(primaryInsuranceAmount, age60Year + 2);
|
|
29
|
-
const disabilityPIA = retirementDate > dates.fullRetirement ? 0 : Math.floor(colaAdjustedPIA);
|
|
30
|
-
const results = retirementDateAdjustedPayment(dates, colaAdjustedPIA);
|
|
54
|
+
const dateStartCounting = new Date(birthday);
|
|
55
|
+
dateStartCounting.setFullYear(yearStartCounting);
|
|
56
|
+
const monthsDiff = monthsDifference(retirementDate, dateStartCounting) / 12;
|
|
57
|
+
const totalYears = Math.min(LOOKBACK_YEARS, monthsDiff);
|
|
31
58
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
birthday,
|
|
60
|
+
retirementDate,
|
|
61
|
+
earnings,
|
|
62
|
+
dates,
|
|
63
|
+
yearAge60,
|
|
64
|
+
yearStartCounting,
|
|
65
|
+
dateStartCounting,
|
|
66
|
+
totalYears
|
|
36
67
|
};
|
|
37
68
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (dates.retirementDate < dates.earliestRetirement) {
|
|
43
|
-
adjustedBenefits = 0;
|
|
69
|
+
// Extracted validation logic
|
|
70
|
+
function validateInputs(birthday, retirementDate, earnings) {
|
|
71
|
+
if (!birthday || !retirementDate) {
|
|
72
|
+
throw new Error('Birthday and retirement date are required');
|
|
44
73
|
}
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
if (!earnings || earnings.length === 0) {
|
|
75
|
+
throw new Error('Earnings history cannot be empty');
|
|
47
76
|
}
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
if (retirementDate < birthday) {
|
|
78
|
+
throw new Error('Retirement date cannot be before birthday');
|
|
50
79
|
}
|
|
51
|
-
const monthlyBenefit = Math.floor(adjustedBenefits);
|
|
52
|
-
return monthlyBenefit;
|
|
53
80
|
}
|
|
81
|
+
// Retirement date calculations
|
|
54
82
|
export function calculateRetirementDates(birthday, retirementDate) {
|
|
55
83
|
const eclBirthDate = getEnglishCommonLawDate(birthday);
|
|
56
84
|
const fraMonths = getFullRetirementMonths(eclBirthDate);
|
|
@@ -67,6 +95,22 @@ export function calculateRetirementDates(birthday, retirementDate) {
|
|
|
67
95
|
retirementDate
|
|
68
96
|
};
|
|
69
97
|
}
|
|
98
|
+
// Benefit amount calculations
|
|
99
|
+
export function retirementDateAdjustedPayment(dates, colaAdjustedPIA) {
|
|
100
|
+
const earlyRetireMonths = monthsDifference(dates.adjusted, dates.fullRetirement);
|
|
101
|
+
let adjustedBenefits = colaAdjustedPIA;
|
|
102
|
+
if (dates.retirementDate < dates.earliestRetirement) {
|
|
103
|
+
adjustedBenefits = 0;
|
|
104
|
+
}
|
|
105
|
+
else if (earlyRetireMonths < 0) {
|
|
106
|
+
adjustedBenefits = calculateEarlyRetirementReduction(colaAdjustedPIA, Math.abs(earlyRetireMonths));
|
|
107
|
+
}
|
|
108
|
+
else if (earlyRetireMonths > 0) {
|
|
109
|
+
adjustedBenefits = calculateDelayedRetirementIncrease(dates.eclBirthDate, colaAdjustedPIA, earlyRetireMonths);
|
|
110
|
+
}
|
|
111
|
+
return Math.floor(adjustedBenefits);
|
|
112
|
+
}
|
|
113
|
+
// COLA adjustments
|
|
70
114
|
function calculateCOLAAdjustments(PIA, startYear) {
|
|
71
115
|
const currentYear = new Date().getFullYear();
|
|
72
116
|
const colaRates = wageIndex
|
|
@@ -77,12 +121,10 @@ function calculateCOLAAdjustments(PIA, startYear) {
|
|
|
77
121
|
return roundToFloorTenCents(adjustedAmount * multiplier);
|
|
78
122
|
}, PIA);
|
|
79
123
|
}
|
|
124
|
+
// PIA calculation
|
|
80
125
|
export function calculatePIA(AIME, baseYear) {
|
|
81
126
|
const effectiveYear = baseYear ? Math.min(baseYear, WAGE_INDEX_CUTOFF) : WAGE_INDEX_CUTOFF;
|
|
82
|
-
const wageIndexEntry =
|
|
83
|
-
if (!wageIndexEntry) {
|
|
84
|
-
throw new Error(`No wage index data found for year ${effectiveYear}`);
|
|
85
|
-
}
|
|
127
|
+
const wageIndexEntry = getWageIndexEntry(effectiveYear);
|
|
86
128
|
const wageIndexLastYear = wageIndexEntry.awi;
|
|
87
129
|
// Calculate bend points (rounded to nearest dollar per SSA rules)
|
|
88
130
|
const firstBendPoint = Math.round(FIRST_BEND_POINT_MULTIPLIER * wageIndexLastYear / BEND_POINT_DIVISOR);
|
|
@@ -104,23 +146,13 @@ export function calculatePIA(AIME, baseYear) {
|
|
|
104
146
|
}
|
|
105
147
|
return roundToFloorTenCents(monthlyBenefit);
|
|
106
148
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const dropOutYears = Math.min(Math.floor(elapsedYears / DROP_OUT_YEARS_DIVISOR), MAX_DROP_OUT_YEARS);
|
|
110
|
-
const adjustedLookbackYears = elapsedYears - dropOutYears;
|
|
111
|
-
return Math.max(minComputationYears, adjustedLookbackYears);
|
|
112
|
-
}
|
|
113
|
-
export function calculateAIME(earnings, yearStartCounter, effectiveDay, baseYear) {
|
|
114
|
-
const totalYears = Math.min(LOOKBACK_YEARS, effectiveDay.getFullYear() - yearStartCounter);
|
|
115
|
-
const lookbackYears = getLookbackYears(totalYears);
|
|
149
|
+
// AIME calculation
|
|
150
|
+
export function calculateAIME(earnings, lookbackYears, baseYear) {
|
|
116
151
|
if (!earnings || earnings.length === 0) {
|
|
117
152
|
return 0;
|
|
118
153
|
}
|
|
119
154
|
const effectiveYear = baseYear ? Math.min(baseYear, WAGE_INDEX_CUTOFF) : WAGE_INDEX_CUTOFF;
|
|
120
|
-
const wageIndexEntry =
|
|
121
|
-
if (!wageIndexEntry) {
|
|
122
|
-
throw new Error(`No wage index data found for year ${effectiveYear}`);
|
|
123
|
-
}
|
|
155
|
+
const wageIndexEntry = getWageIndexEntry(effectiveYear);
|
|
124
156
|
const wageIndexLastYear = wageIndexEntry.awi;
|
|
125
157
|
const futureYearsFactor = 1;
|
|
126
158
|
// Calculate wage index factors
|
|
@@ -133,15 +165,27 @@ export function calculateAIME(earnings, yearStartCounter, effectiveDay, baseYear
|
|
|
133
165
|
acc[year] = earnings * (wageIndexFactors[year] || futureYearsFactor);
|
|
134
166
|
return acc;
|
|
135
167
|
}, {});
|
|
136
|
-
// Get top
|
|
137
|
-
const
|
|
168
|
+
// Get top years of earnings based on lookback period
|
|
169
|
+
const topYearsEarningsArr = Object.values(adjustedEarnings)
|
|
138
170
|
.sort((a, b) => b - a)
|
|
139
171
|
.slice(0, lookbackYears);
|
|
140
|
-
const totalEarnings =
|
|
172
|
+
const totalEarnings = topYearsEarningsArr.reduce((sum, earnings) => sum + earnings, 0);
|
|
141
173
|
// Calculate AIME (rounded down to next lower dollar)
|
|
142
|
-
|
|
143
|
-
|
|
174
|
+
return Math.floor(totalEarnings / (12 * lookbackYears));
|
|
175
|
+
}
|
|
176
|
+
// Lookback year calculations
|
|
177
|
+
export function getLookbackYears(elapsedYears) {
|
|
178
|
+
const minComputationYears = 2;
|
|
179
|
+
const adjustedLookbackYears = Math.floor(elapsedYears) - 5;
|
|
180
|
+
return Math.max(minComputationYears, adjustedLookbackYears);
|
|
181
|
+
}
|
|
182
|
+
export function getLookbackYearsDisability(elapsedYears) {
|
|
183
|
+
const minComputationYears = 2;
|
|
184
|
+
const dropOutYears = Math.min(Math.floor(elapsedYears / DROP_OUT_YEARS_DIVISOR), MAX_DROP_OUT_YEARS);
|
|
185
|
+
const adjustedLookbackYears = elapsedYears - dropOutYears;
|
|
186
|
+
return Math.max(minComputationYears, adjustedLookbackYears);
|
|
144
187
|
}
|
|
188
|
+
// Early retirement reduction
|
|
145
189
|
function calculateEarlyRetirementReduction(amount, months) {
|
|
146
190
|
if (months <= 0)
|
|
147
191
|
return amount;
|
|
@@ -156,6 +200,7 @@ function calculateEarlyRetirementReduction(amount, months) {
|
|
|
156
200
|
}
|
|
157
201
|
return amount * (1 - reduction);
|
|
158
202
|
}
|
|
203
|
+
// Delayed retirement increase
|
|
159
204
|
function calculateDelayedRetirementIncrease(birthday, initialAmount, numberOfMonths) {
|
|
160
205
|
if (numberOfMonths <= 0)
|
|
161
206
|
return initialAmount;
|
|
@@ -164,11 +209,11 @@ function calculateDelayedRetirementIncrease(birthday, initialAmount, numberOfMon
|
|
|
164
209
|
const totalIncrease = monthlyRate * numberOfMonths;
|
|
165
210
|
return initialAmount * (1 + totalIncrease);
|
|
166
211
|
}
|
|
212
|
+
// Delayed retirement rate lookup
|
|
167
213
|
function getDelayedRetirementRate(birthYear) {
|
|
168
214
|
if (birthYear < 1933) {
|
|
169
215
|
throw new Error(`Invalid birth year for delayed retirement: ${birthYear}`);
|
|
170
216
|
}
|
|
171
|
-
// Rates based on SSA rules
|
|
172
217
|
if (birthYear <= 1934)
|
|
173
218
|
return 11 / 24 / 100; // 11/24 of 1%
|
|
174
219
|
if (birthYear <= 1936)
|
|
@@ -181,30 +226,19 @@ function getDelayedRetirementRate(birthYear) {
|
|
|
181
226
|
return 5 / 8 / 100; // 5/8 of 1%
|
|
182
227
|
return 2 / 3 / 100; // 2/3 of 1%
|
|
183
228
|
}
|
|
184
|
-
|
|
185
|
-
// Convert to dimes, floor, then convert back to dollars
|
|
186
|
-
return Math.floor(amount * 10) / 10;
|
|
187
|
-
}
|
|
188
|
-
export function getEnglishCommonLawDate(date) {
|
|
189
|
-
// Create a new date to avoid mutating the original
|
|
190
|
-
const eclDate = new Date(date);
|
|
191
|
-
eclDate.setDate(eclDate.getDate() - 1);
|
|
192
|
-
return eclDate;
|
|
193
|
-
}
|
|
229
|
+
// Full retirement age calculation
|
|
194
230
|
export function getFullRetirementMonths(commonLawBirthDate) {
|
|
195
231
|
const year = commonLawBirthDate.getFullYear();
|
|
196
232
|
if (year <= 1937) {
|
|
197
233
|
return 65 * 12;
|
|
198
234
|
}
|
|
199
235
|
else if (year <= 1942) {
|
|
200
|
-
// Gradual increase from 65 years to 65 years 10 months
|
|
201
236
|
return ((year - 1937) * 2) + (65 * 12);
|
|
202
237
|
}
|
|
203
238
|
else if (year <= 1954) {
|
|
204
239
|
return 66 * 12;
|
|
205
240
|
}
|
|
206
241
|
else if (year <= 1959) {
|
|
207
|
-
// Gradual increase from 66 years to 66 years 10 months
|
|
208
242
|
return ((year - 1954) * 2) + (66 * 12);
|
|
209
243
|
}
|
|
210
244
|
else if (year >= 1960) {
|
|
@@ -214,8 +248,42 @@ export function getFullRetirementMonths(commonLawBirthDate) {
|
|
|
214
248
|
throw new Error(`Invalid birth year: ${year}`);
|
|
215
249
|
}
|
|
216
250
|
}
|
|
251
|
+
/** Compute Family-Max bend points for an eligibility year given AWI_{year-2}. */
|
|
252
|
+
export function getFamilyMaxBendPoints(baseYear) {
|
|
253
|
+
const [f1, f2, f3] = FAM_MAX_BASES.map(b => Math.round((b * baseYear / BEND_POINT_DIVISOR)));
|
|
254
|
+
return [f1, f2, f3];
|
|
255
|
+
}
|
|
256
|
+
/** Apply the family-maximum formula to a PIA using already-computed bend points. */
|
|
257
|
+
export function calculateFamilyMaximum(pia, [bp1, bp2, bp3]) {
|
|
258
|
+
let max = 0;
|
|
259
|
+
max += 1.50 * Math.min(pia, bp1);
|
|
260
|
+
if (pia > bp1)
|
|
261
|
+
max += 2.72 * (Math.min(pia, bp2) - bp1);
|
|
262
|
+
if (pia > bp2)
|
|
263
|
+
max += 1.34 * (Math.min(pia, bp3) - bp2);
|
|
264
|
+
if (pia > bp3)
|
|
265
|
+
max += 1.75 * (pia - bp3);
|
|
266
|
+
// SSA: round total down to next lower $0.10
|
|
267
|
+
return Math.floor(max * 10) / 10;
|
|
268
|
+
}
|
|
269
|
+
// Utility functions
|
|
270
|
+
export function getEnglishCommonLawDate(date) {
|
|
271
|
+
const eclDate = new Date(date);
|
|
272
|
+
eclDate.setDate(eclDate.getDate() - 1);
|
|
273
|
+
return eclDate;
|
|
274
|
+
}
|
|
217
275
|
function monthsDifference(date1, date2) {
|
|
218
276
|
const months1 = date1.getFullYear() * 12 + date1.getMonth();
|
|
219
277
|
const months2 = date2.getFullYear() * 12 + date2.getMonth();
|
|
220
278
|
return months1 - months2;
|
|
221
279
|
}
|
|
280
|
+
function roundToFloorTenCents(amount) {
|
|
281
|
+
return Math.floor(amount * 10) / 10;
|
|
282
|
+
}
|
|
283
|
+
function getWageIndexEntry(effectiveYear) {
|
|
284
|
+
const wageIndexEntry = wageIndex.find(val => val.year === effectiveYear);
|
|
285
|
+
if (!wageIndexEntry) {
|
|
286
|
+
throw new Error(`No wage index data found for year ${effectiveYear}`);
|
|
287
|
+
}
|
|
288
|
+
return wageIndexEntry;
|
|
289
|
+
}
|
package/lib/model.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ export interface BenefitCalculationResult {
|
|
|
13
13
|
PIA: number;
|
|
14
14
|
NormalMonthlyBenefit: number;
|
|
15
15
|
DisabilityEarnings: number;
|
|
16
|
+
SurvivorBenefits: {
|
|
17
|
+
survivingChild: number;
|
|
18
|
+
careGivingSpouse: number;
|
|
19
|
+
normalRetirementSpouse: number;
|
|
20
|
+
familyMaximum: number;
|
|
21
|
+
};
|
|
16
22
|
}
|
|
17
23
|
export interface RetirementDates {
|
|
18
24
|
earliestRetirement: Date;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "social-security-calculator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Calculate estimated Social Security Benefits",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"minimist": "^1.2.8",
|
|
48
48
|
"playwright": "^1.54.1",
|
|
49
49
|
"ts-jest": "^29.1.1",
|
|
50
|
-
"typescript": "^
|
|
50
|
+
"typescript": "^5.9.2"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"xml2js": "^0.6.2"
|