social-security-calculator 2.0.0 → 3.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.
@@ -1,5 +1,5 @@
1
1
  export declare const EARLY_RETIREMENT_AGE = 62;
2
- export declare const WAGE_INDEX_CUTOFF = 2023;
2
+ export declare const WAGE_INDEX_CUTOFF = 2024;
3
3
  export declare const MAX_RETIREMENT_AGE = 70;
4
4
  export declare const LOOKBACK_YEARS = 40;
5
5
  export declare const MAX_DROP_OUT_YEARS = 5;
@@ -8,7 +8,9 @@ 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;
13
+ export declare const DISABILITY_LAST_EARNINGS_YEARS_THRESHOLD = 5;
12
14
  export declare const PIA_PERCENTAGES: {
13
15
  readonly FIRST_BRACKET: 0.9;
14
16
  readonly SECOND_BRACKET: 0.32;
package/lib/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Constants
2
2
  export const EARLY_RETIREMENT_AGE = 62;
3
- export const WAGE_INDEX_CUTOFF = 2023;
3
+ export const WAGE_INDEX_CUTOFF = 2024;
4
4
  export const MAX_RETIREMENT_AGE = 70;
5
5
  // LOOKBACK_YEARS This represents the working years between 22 and 62,
6
6
  // but it does not filter out the wages of years before and after that range.
@@ -13,7 +13,10 @@ 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
18
+ // Disability 5-year rule: if disability date is more than 5 years after last earnings year, disability benefit is zero
19
+ export const DISABILITY_LAST_EARNINGS_YEARS_THRESHOLD = 5;
17
20
  export const PIA_PERCENTAGES = {
18
21
  FIRST_BRACKET: 0.9,
19
22
  SECOND_BRACKET: 0.32,
@@ -21,6 +24,6 @@ export const PIA_PERCENTAGES = {
21
24
  };
22
25
  export const EARLY_RETIREMENT_REDUCTION = {
23
26
  FIRST_MONTHS: 36,
24
- FIRST_MONTHS_RATE: 5 / 9 * 0.01,
27
+ FIRST_MONTHS_RATE: 5 / 9 * 0.01, // 5/9 of 1%
25
28
  ADDITIONAL_MONTHS_RATE: 5 / 12 * 0.01 // 5/12 of 1%
26
29
  };
package/lib/index.d.ts CHANGED
@@ -1,9 +1,6 @@
1
- import { BenefitCalculationResult, RetirementDates, Earnings } from './model';
1
+ import { BenefitCalculationResult, Earnings } from './model';
2
2
  export declare function calc(birthday: Date, retirementDate: Date, earnings: Earnings): BenefitCalculationResult;
3
- export declare function retirementDateAdjustedPayment(dates: RetirementDates, colaAdjustedPIA: number): number;
4
- export declare function calculateRetirementDates(birthday: Date, retirementDate: Date): RetirementDates;
3
+ export declare function calcRetirement(birthday: Date, retirementDate: Date, earnings: Earnings): Partial<BenefitCalculationResult>;
5
4
  export declare function calculatePIA(AIME: number, baseYear?: number): number;
6
- export declare function getLookbackYears(elapsedYears: number): number;
7
- export declare function calculateAIME(earnings: Earnings, yearStartCounter: number, effectiveDay: Date, baseYear?: number): number;
5
+ export declare function calculateAIME(earnings: Earnings, lookbackYears: number, baseYear?: number): number;
8
6
  export declare function getEnglishCommonLawDate(date: Date): Date;
9
- export declare function getFullRetirementMonths(commonLawBirthDate: Date): number;
package/lib/index.js CHANGED
@@ -1,57 +1,89 @@
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 calculation function
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, DISABILITY_LAST_EARNINGS_YEARS_THRESHOLD } from './constants';
3
+ // Main entry point
4
4
  export function calc(birthday, retirementDate, earnings) {
5
- // Validation
6
- if (!birthday || !retirementDate) {
7
- throw new Error('Birthday and retirement date are required');
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
+ // Apply disability 5-year rule: if disability date is more than 5 years after last earnings year, benefit is zero
17
+ let disabilityPIAFloored = 0;
18
+ if (context.retirementDate <= context.dates.fullRetirement && !isDisabilityDateBeyondFiveYearRule(retirementDate, earnings)) {
19
+ disabilityPIAFloored = Math.floor(disabilityBenefit.colaAdjustedPIA);
8
20
  }
21
+ return {
22
+ AIME: regularBenefit.aime,
23
+ PIA: regularBenefit.pia,
24
+ NormalMonthlyBenefit: monthlyBenefit,
25
+ DisabilityEarnings: disabilityPIAFloored,
26
+ };
27
+ }
28
+ // Reusable benefit calculation pipeline
29
+ function calculateBenefitPipeline(earnings, yearAge60, lookbackYears) {
30
+ const aime = calculateAIME(earnings, lookbackYears, yearAge60);
31
+ const pia = calculatePIA(aime, yearAge60);
32
+ const colaAdjustedPIA = calculateCOLAAdjustments(pia, yearAge60 + 2);
33
+ return { aime, pia, colaAdjustedPIA };
34
+ }
35
+ // Survivor benefits calculation
36
+ function calcSurvivor(birthday, retirementDate, earnings) {
37
+ const context = createCalculationContext(birthday, retirementDate, earnings);
38
+ const benefit = calculateBenefitPipeline(earnings, context.yearAge60, getLookbackYears(context.totalYears));
39
+ const survivorPIA = Math.floor(benefit.colaAdjustedPIA * CHILD_SURVIVOR_BENEFIT_PERCENTAGE);
40
+ const effectiveYear = Math.min(context.yearAge60, WAGE_INDEX_CUTOFF);
41
+ const wageIndexEntry = getWageIndexEntry(effectiveYear);
42
+ const wageIndexLastYear = wageIndexEntry.awi;
43
+ const familyMax = calculateFamilyMaximum(benefit.pia, getFamilyMaxBendPoints(wageIndexLastYear));
44
+ const colaAdjustedFamMax = calculateCOLAAdjustments(familyMax, context.yearAge60 + 2);
45
+ return {
46
+ survivingChild: survivorPIA,
47
+ careGivingSpouse: survivorPIA,
48
+ normalRetirementSpouse: Math.floor(benefit.colaAdjustedPIA),
49
+ familyMaximum: colaAdjustedFamMax
50
+ };
51
+ }
52
+ // Create shared calculation context to avoid duplication
53
+ function createCalculationContext(birthday, retirementDate, earnings) {
54
+ validateInputs(birthday, retirementDate, earnings);
9
55
  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
56
  const yearAge60 = birthday.getFullYear() + 60;
21
57
  const yearStartCounting = birthday.getFullYear() + ELAPSED_YEARS_START_AGE - 1;
22
- const averageIndexedMonthlyEarnings = calculateAIME(earnings, yearStartCounting, retirementDate, yearAge60);
23
- const age60Year = dates.eclBirthDate.getFullYear() + 60;
24
- const primaryInsuranceAmount = calculatePIA(averageIndexedMonthlyEarnings, age60Year);
25
- const survivorPIA = primaryInsuranceAmount * CHILD_SURVIVOR_BENEFIT_PERCENTAGE;
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);
58
+ const dateStartCounting = new Date(birthday);
59
+ dateStartCounting.setFullYear(yearStartCounting);
60
+ const monthsDiff = monthsDifference(retirementDate, dateStartCounting) / 12;
61
+ const totalYears = Math.min(LOOKBACK_YEARS, monthsDiff);
31
62
  return {
32
- AIME: averageIndexedMonthlyEarnings,
33
- PIA: primaryInsuranceAmount,
34
- NormalMonthlyBenefit: results,
35
- DisabilityEarnings: disabilityPIA,
63
+ birthday,
64
+ retirementDate,
65
+ earnings,
66
+ dates,
67
+ yearAge60,
68
+ yearStartCounting,
69
+ dateStartCounting,
70
+ totalYears
36
71
  };
37
72
  }
38
- export function retirementDateAdjustedPayment(dates, colaAdjustedPIA) {
39
- // Calculate early/delayed retirement adjustments
40
- const earlyRetireMonths = monthsDifference(dates.adjusted, dates.fullRetirement);
41
- let adjustedBenefits = colaAdjustedPIA;
42
- if (dates.retirementDate < dates.earliestRetirement) {
43
- adjustedBenefits = 0;
73
+ // Extracted validation logic
74
+ function validateInputs(birthday, retirementDate, earnings) {
75
+ if (!birthday || !retirementDate) {
76
+ throw new Error('Birthday and retirement date are required');
44
77
  }
45
- else if (earlyRetireMonths < 0) {
46
- adjustedBenefits = calculateEarlyRetirementReduction(colaAdjustedPIA, Math.abs(earlyRetireMonths));
78
+ if (!earnings || earnings.length === 0) {
79
+ throw new Error('Earnings history cannot be empty');
47
80
  }
48
- else if (earlyRetireMonths > 0) {
49
- adjustedBenefits = calculateDelayedRetirementIncrease(dates.eclBirthDate, colaAdjustedPIA, earlyRetireMonths);
81
+ if (retirementDate < birthday) {
82
+ throw new Error('Retirement date cannot be before birthday');
50
83
  }
51
- const monthlyBenefit = Math.floor(adjustedBenefits);
52
- return monthlyBenefit;
53
84
  }
54
- export function calculateRetirementDates(birthday, retirementDate) {
85
+ // Retirement date calculations
86
+ function calculateRetirementDates(birthday, retirementDate) {
55
87
  const eclBirthDate = getEnglishCommonLawDate(birthday);
56
88
  const fraMonths = getFullRetirementMonths(eclBirthDate);
57
89
  const earliestRetirement = new Date(eclBirthDate.getFullYear() + EARLY_RETIREMENT_AGE, eclBirthDate.getMonth(), eclBirthDate.getDate());
@@ -67,6 +99,22 @@ export function calculateRetirementDates(birthday, retirementDate) {
67
99
  retirementDate
68
100
  };
69
101
  }
102
+ // Benefit amount calculations
103
+ function retirementDateAdjustedPayment(dates, colaAdjustedPIA) {
104
+ const earlyRetireMonths = monthsDifference(dates.adjusted, dates.fullRetirement);
105
+ let adjustedBenefits = colaAdjustedPIA;
106
+ if (dates.retirementDate < dates.earliestRetirement) {
107
+ adjustedBenefits = 0;
108
+ }
109
+ else if (earlyRetireMonths < 0) {
110
+ adjustedBenefits = calculateEarlyRetirementReduction(colaAdjustedPIA, Math.abs(earlyRetireMonths));
111
+ }
112
+ else if (earlyRetireMonths > 0) {
113
+ adjustedBenefits = calculateDelayedRetirementIncrease(dates.eclBirthDate, colaAdjustedPIA, earlyRetireMonths);
114
+ }
115
+ return Math.floor(adjustedBenefits);
116
+ }
117
+ // COLA adjustments
70
118
  function calculateCOLAAdjustments(PIA, startYear) {
71
119
  const currentYear = new Date().getFullYear();
72
120
  const colaRates = wageIndex
@@ -77,12 +125,10 @@ function calculateCOLAAdjustments(PIA, startYear) {
77
125
  return roundToFloorTenCents(adjustedAmount * multiplier);
78
126
  }, PIA);
79
127
  }
128
+ // PIA calculation
80
129
  export function calculatePIA(AIME, baseYear) {
81
130
  const effectiveYear = baseYear ? Math.min(baseYear, WAGE_INDEX_CUTOFF) : WAGE_INDEX_CUTOFF;
82
- const wageIndexEntry = wageIndex.find(val => val.year === effectiveYear);
83
- if (!wageIndexEntry) {
84
- throw new Error(`No wage index data found for year ${effectiveYear}`);
85
- }
131
+ const wageIndexEntry = getWageIndexEntry(effectiveYear);
86
132
  const wageIndexLastYear = wageIndexEntry.awi;
87
133
  // Calculate bend points (rounded to nearest dollar per SSA rules)
88
134
  const firstBendPoint = Math.round(FIRST_BEND_POINT_MULTIPLIER * wageIndexLastYear / BEND_POINT_DIVISOR);
@@ -104,23 +150,13 @@ export function calculatePIA(AIME, baseYear) {
104
150
  }
105
151
  return roundToFloorTenCents(monthlyBenefit);
106
152
  }
107
- export function getLookbackYears(elapsedYears) {
108
- const minComputationYears = 2;
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);
153
+ // AIME calculation
154
+ export function calculateAIME(earnings, lookbackYears, baseYear) {
116
155
  if (!earnings || earnings.length === 0) {
117
156
  return 0;
118
157
  }
119
158
  const effectiveYear = baseYear ? Math.min(baseYear, WAGE_INDEX_CUTOFF) : WAGE_INDEX_CUTOFF;
120
- const wageIndexEntry = wageIndex.find(val => val.year === effectiveYear);
121
- if (!wageIndexEntry) {
122
- throw new Error(`No wage index data found for year ${effectiveYear}`);
123
- }
159
+ const wageIndexEntry = getWageIndexEntry(effectiveYear);
124
160
  const wageIndexLastYear = wageIndexEntry.awi;
125
161
  const futureYearsFactor = 1;
126
162
  // Calculate wage index factors
@@ -133,15 +169,27 @@ export function calculateAIME(earnings, yearStartCounter, effectiveDay, baseYear
133
169
  acc[year] = earnings * (wageIndexFactors[year] || futureYearsFactor);
134
170
  return acc;
135
171
  }, {});
136
- // Get top 35 years of earnings
137
- const top35YearsEarningsArr = Object.values(adjustedEarnings)
172
+ // Get top years of earnings based on lookback period
173
+ const topYearsEarningsArr = Object.values(adjustedEarnings)
138
174
  .sort((a, b) => b - a)
139
175
  .slice(0, lookbackYears);
140
- const totalEarnings = top35YearsEarningsArr.reduce((sum, earnings) => sum + earnings, 0);
176
+ const totalEarnings = topYearsEarningsArr.reduce((sum, earnings) => sum + earnings, 0);
141
177
  // Calculate AIME (rounded down to next lower dollar)
142
- const averageIndexedMonthlyEarnings = Math.floor(totalEarnings / (12 * lookbackYears));
143
- return averageIndexedMonthlyEarnings;
178
+ return Math.floor(totalEarnings / (12 * lookbackYears));
144
179
  }
180
+ // Lookback year calculations
181
+ function getLookbackYears(elapsedYears) {
182
+ const minComputationYears = 2;
183
+ const adjustedLookbackYears = Math.floor(elapsedYears) - 5;
184
+ return Math.max(minComputationYears, adjustedLookbackYears);
185
+ }
186
+ function getLookbackYearsDisability(elapsedYears) {
187
+ const minComputationYears = 2;
188
+ const dropOutYears = Math.min(Math.floor(elapsedYears / DROP_OUT_YEARS_DIVISOR), MAX_DROP_OUT_YEARS);
189
+ const adjustedLookbackYears = elapsedYears - dropOutYears;
190
+ return Math.max(minComputationYears, adjustedLookbackYears);
191
+ }
192
+ // Early retirement reduction
145
193
  function calculateEarlyRetirementReduction(amount, months) {
146
194
  if (months <= 0)
147
195
  return amount;
@@ -156,6 +204,7 @@ function calculateEarlyRetirementReduction(amount, months) {
156
204
  }
157
205
  return amount * (1 - reduction);
158
206
  }
207
+ // Delayed retirement increase
159
208
  function calculateDelayedRetirementIncrease(birthday, initialAmount, numberOfMonths) {
160
209
  if (numberOfMonths <= 0)
161
210
  return initialAmount;
@@ -164,11 +213,11 @@ function calculateDelayedRetirementIncrease(birthday, initialAmount, numberOfMon
164
213
  const totalIncrease = monthlyRate * numberOfMonths;
165
214
  return initialAmount * (1 + totalIncrease);
166
215
  }
216
+ // Delayed retirement rate lookup
167
217
  function getDelayedRetirementRate(birthYear) {
168
218
  if (birthYear < 1933) {
169
219
  throw new Error(`Invalid birth year for delayed retirement: ${birthYear}`);
170
220
  }
171
- // Rates based on SSA rules
172
221
  if (birthYear <= 1934)
173
222
  return 11 / 24 / 100; // 11/24 of 1%
174
223
  if (birthYear <= 1936)
@@ -181,30 +230,19 @@ function getDelayedRetirementRate(birthYear) {
181
230
  return 5 / 8 / 100; // 5/8 of 1%
182
231
  return 2 / 3 / 100; // 2/3 of 1%
183
232
  }
184
- function roundToFloorTenCents(amount) {
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
- }
194
- export function getFullRetirementMonths(commonLawBirthDate) {
233
+ // Full retirement age calculation
234
+ function getFullRetirementMonths(commonLawBirthDate) {
195
235
  const year = commonLawBirthDate.getFullYear();
196
236
  if (year <= 1937) {
197
237
  return 65 * 12;
198
238
  }
199
239
  else if (year <= 1942) {
200
- // Gradual increase from 65 years to 65 years 10 months
201
240
  return ((year - 1937) * 2) + (65 * 12);
202
241
  }
203
242
  else if (year <= 1954) {
204
243
  return 66 * 12;
205
244
  }
206
245
  else if (year <= 1959) {
207
- // Gradual increase from 66 years to 66 years 10 months
208
246
  return ((year - 1954) * 2) + (66 * 12);
209
247
  }
210
248
  else if (year >= 1960) {
@@ -214,8 +252,61 @@ export function getFullRetirementMonths(commonLawBirthDate) {
214
252
  throw new Error(`Invalid birth year: ${year}`);
215
253
  }
216
254
  }
255
+ /** Compute Family-Max bend points for an eligibility year given AWI_{year-2}. */
256
+ function getFamilyMaxBendPoints(baseYear) {
257
+ const [f1, f2, f3] = FAM_MAX_BASES.map(b => Math.round((b * baseYear / BEND_POINT_DIVISOR)));
258
+ return [f1, f2, f3];
259
+ }
260
+ /** Apply the family-maximum formula to a PIA using already-computed bend points. */
261
+ function calculateFamilyMaximum(pia, [bp1, bp2, bp3]) {
262
+ let max = 0;
263
+ max += 1.50 * Math.min(pia, bp1);
264
+ if (pia > bp1)
265
+ max += 2.72 * (Math.min(pia, bp2) - bp1);
266
+ if (pia > bp2)
267
+ max += 1.34 * (Math.min(pia, bp3) - bp2);
268
+ if (pia > bp3)
269
+ max += 1.75 * (pia - bp3);
270
+ // SSA: round total down to next lower $0.10
271
+ return Math.floor(max * 10) / 10;
272
+ }
273
+ // Check if disability date violates the 5-year rule
274
+ function isDisabilityDateBeyondFiveYearRule(disabilityDate, earnings) {
275
+ if (!earnings || earnings.length === 0) {
276
+ return true; // No earnings means no disability benefit
277
+ }
278
+ // Find the last year with non-zero earnings
279
+ const earningsWithValue = earnings.filter(e => e.earnings > 0);
280
+ if (earningsWithValue.length === 0) {
281
+ return true; // No non-zero earnings means no disability benefit
282
+ }
283
+ const lastEarningsYear = Math.max(...earningsWithValue.map(e => e.year));
284
+ // Last day of the last earnings year
285
+ const lastDayOfLastEarningsYear = new Date(lastEarningsYear, 11, 31); // December 31st
286
+ // Add exactly 5 years to the last day of last earnings year
287
+ const fiveYearsLater = new Date(lastDayOfLastEarningsYear);
288
+ fiveYearsLater.setFullYear(fiveYearsLater.getFullYear() + DISABILITY_LAST_EARNINGS_YEARS_THRESHOLD);
289
+ // Disability date must be after the 5-year threshold
290
+ return disabilityDate > fiveYearsLater;
291
+ }
292
+ // Utility functions
293
+ export function getEnglishCommonLawDate(date) {
294
+ const eclDate = new Date(date);
295
+ eclDate.setDate(eclDate.getDate() - 1);
296
+ return eclDate;
297
+ }
217
298
  function monthsDifference(date1, date2) {
218
299
  const months1 = date1.getFullYear() * 12 + date1.getMonth();
219
300
  const months2 = date2.getFullYear() * 12 + date2.getMonth();
220
301
  return months1 - months2;
221
302
  }
303
+ function roundToFloorTenCents(amount) {
304
+ return Math.floor(amount * 10) / 10;
305
+ }
306
+ function getWageIndexEntry(effectiveYear) {
307
+ const wageIndexEntry = wageIndex.find(val => val.year === effectiveYear);
308
+ if (!wageIndexEntry) {
309
+ throw new Error(`No wage index data found for year ${effectiveYear}`);
310
+ }
311
+ return wageIndexEntry;
312
+ }
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/lib/wage-index.js CHANGED
@@ -442,22 +442,28 @@ export const wageIndex = [
442
442
  "year": 2024,
443
443
  "taxMax": 168600,
444
444
  "cola": 2.5,
445
- "awi": 69472.44
445
+ "awi": 69846.57
446
446
  },
447
447
  {
448
448
  "year": 2025,
449
449
  "taxMax": 176100,
450
- "cola": 2.7,
451
- "awi": 72255.52
450
+ "cola": 2.8,
451
+ "awi": 72644.64
452
+ },
453
+ {
454
+ "year": 2026,
455
+ "taxMax": 176100,
456
+ "cola": 2.5,
457
+ "awi": 75670.13
452
458
  }
453
459
  ];
460
+ // 2026 60000.00 60000.00 0.00 0.00 176100 2.5 75670.13
454
461
  export const wageIndexFuture = [
455
- { year: 2024, awi: 69472.44 },
456
- { year: 2025, awi: 72255.52 },
457
- { year: 2026, awi: 75264.81 },
458
- { year: 2027, awi: 78304.32 },
459
- { year: 2028, awi: 81522.64 },
460
- { year: 2029, awi: 84736.18 },
462
+ { year: 2025, awi: 72644.64 },
463
+ { year: 2026, awi: 75670.13 },
464
+ { year: 2027, awi: 78726.01 },
465
+ { year: 2028, awi: 81961.66 },
466
+ { year: 2029, awi: 85192.51 },
461
467
  { year: 2030, awi: 88030.45 },
462
468
  { year: 2031, awi: 91479.46 },
463
469
  { year: 2032, awi: 95090.94 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-security-calculator",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Calculate estimated Social Security Benefits",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -40,6 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@jest/globals": "^29.7.0",
42
42
  "@types/jest": "^29.5.8",
43
+ "@types/minimist": "^1.2.5",
43
44
  "@types/xml2js": "^0.4.11",
44
45
  "compound-calc": "^4.0.3",
45
46
  "csvtojson": "^2.0.10",
@@ -47,7 +48,7 @@
47
48
  "minimist": "^1.2.8",
48
49
  "playwright": "^1.54.1",
49
50
  "ts-jest": "^29.1.1",
50
- "typescript": "^4.8.3"
51
+ "typescript": "^5.9.2"
51
52
  },
52
53
  "dependencies": {
53
54
  "xml2js": "^0.6.2"