social-security-calculator 0.0.6 → 0.0.7

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.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2022 RYAN ANTKOWIAK, CHARLES MCNULTY
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
4
+ files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
5
+ modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
6
+ Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
11
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
12
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
13
+ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -4,14 +4,11 @@ TypeScript to calculate estimated Social Security Benefits
4
4
 
5
5
  This module will calculate your expected retirement benefits
6
6
  from Social Security given your annual earnings
7
- Inputs:
8
- 1) EarningsRecord -
9
- Dictionary mapping a year to the amount of Social
10
- Security eligible earnings in that particular year
11
7
 
12
- 2) NationalAverageWageIndexSeries -
13
- Data pulled directly from the Social Security website for the
14
- national average wage data
8
+ Input:
15
9
 
16
- Adapted from python originally written by Ryan Antkowiak (antkowiak@gmail.com) and updated by Kevin Fowlks (fowlk1kd@gmail.com)
10
+ Dictionary mapping a year to the amount of Social
11
+ Security eligible earnings in that particular year
17
12
 
13
+
14
+ Adapted from python originally written by Ryan Antkowiak
@@ -0,0 +1,49 @@
1
+ import { wageIndex } from '../wage-index';
2
+ const YOUTH_FACTOR = 8;
3
+ const YOUTH_FACTOR_AGE = 21;
4
+ const WORK_START_AGE = 18;
5
+ const CURRENT_YEAR_INCREASE = .945; //.96
6
+ const CURRENT_YEAR = new Date().getFullYear();
7
+ export function getEstimatedEarnings(age, lastWage, lastYearWorked = CURRENT_YEAR, earningGrowthRate = 0) {
8
+ if (age <= 22) {
9
+ throw new Error('Age must be greater than 22');
10
+ }
11
+ if (lastYearWorked > CURRENT_YEAR) {
12
+ throw new Error('Last year worked cannot be in the future');
13
+ }
14
+ const workStartYear = CURRENT_YEAR - age + WORK_START_AGE;
15
+ const yearTurned22 = CURRENT_YEAR - age + YOUTH_FACTOR_AGE;
16
+ const wageResults = {};
17
+ let i = lastYearWorked;
18
+ for (; i >= workStartYear; i--) {
19
+ const reductionFactor = getReductionFactor(i) / (1 + earningGrowthRate);
20
+ const youthFactor = i === yearTurned22 ? YOUTH_FACTOR : 1;
21
+ const year = i;
22
+ const nextYear = (i + 1);
23
+ if (i === lastYearWorked) {
24
+ wageResults[year] = lastWage;
25
+ }
26
+ else {
27
+ wageResults[year] = (wageResults[nextYear] * reductionFactor) / youthFactor;
28
+ }
29
+ }
30
+ return wageResults;
31
+ }
32
+ function getReductionFactor(year) {
33
+ const lastYear = year - 1;
34
+ const nextYear = year + 1;
35
+ if (year === CURRENT_YEAR && !wageIndex[lastYear]) {
36
+ throw new Error(`Wage index for previous year (${lastYear}) is required`);
37
+ // return CURRENT_YEAR_INCREASE
38
+ }
39
+ if (year === CURRENT_YEAR) {
40
+ return 1;
41
+ }
42
+ else if (year === CURRENT_YEAR - 1) {
43
+ return CURRENT_YEAR_INCREASE;
44
+ }
45
+ else {
46
+ return (wageIndex[year] / wageIndex[nextYear]);
47
+ }
48
+ }
49
+ // console.log(getEstimatedEarnings(70, 100000, 2020, .02));
package/lib/index.js CHANGED
@@ -1,23 +1,32 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const wage_index_1 = require("./wage-index");
4
- function calc(earnings) {
1
+ import { wageIndex } from './wage-index';
2
+ export const calc = (earnings) => {
5
3
  const lookbackYears = 35;
6
4
  const futureYearsFactor = 1;
7
5
  const bendPointDivisor = 9779.44;
8
- const firstBendPointMultiplier = 180.0;
9
- const secondBendPointMultiplier = 1085.0;
10
- const averageWageLastYear = Math.max(...Object.keys(wage_index_1.wageIndex).map(val => parseInt(val)));
11
- const firstBendPoint = Math.round(firstBendPointMultiplier * wage_index_1.wageIndex[averageWageLastYear] / bendPointDivisor);
12
- const secondBendPoint = Math.round(secondBendPointMultiplier * wage_index_1.wageIndex[averageWageLastYear] / bendPointDivisor);
13
- const wageIndexFactors = Object.entries(wage_index_1.wageIndex).reduce((acc, [i, val]) => ((acc[i] = 1 + (wage_index_1.wageIndex[averageWageLastYear] - val) / val) && acc), {});
14
- const adjustedEarnings = Object.entries(earnings).reduce((acc, [i, val]) => ((acc[i] = val * (wageIndexFactors[i] || futureYearsFactor)) && acc), {});
15
- const top35YearsEarnings = Object.values(adjustedEarnings)
6
+ const firstBendPointMultiplier = 180;
7
+ const secondBendPointMultiplier = 1085;
8
+ const averageWageLastYear = Math.max(...Object.keys(wageIndex).map(val => parseInt(val)));
9
+ const wageIndexLastYear = wageIndex[averageWageLastYear];
10
+ // https://www.ssa.gov/oact/COLA/piaformula.html
11
+ // Per examples, bend points are rounded to the nearest dollar
12
+ const firstBendPoint = Math.round(firstBendPointMultiplier * wageIndexLastYear / bendPointDivisor);
13
+ const secondBendPoint = Math.round(secondBendPointMultiplier * wageIndexLastYear / bendPointDivisor);
14
+ // calculate the wage index factors
15
+ const wageIndexFactors = Object.entries(wageIndex).reduce((acc, [i, val]) => ((acc[parseInt(i)] = 1 + (wageIndexLastYear - val) / val), acc), {});
16
+ // adjust the earnings according to the wage index factor,
17
+ // factor is 1 for any earnings record without a wage-factor
18
+ const adjustedEarnings = Object.entries(earnings).reduce((acc, [i, val]) => ((acc[parseInt(i)] = val * (wageIndexFactors[parseInt(i)] || futureYearsFactor)), acc), {});
19
+ const top35YearsEarningsArr = Object.values(adjustedEarnings)
16
20
  .sort((a, b) => b - a) // sort the earnings from highest to lowest amount
17
- .slice(0, lookbackYears) // grab the highest 35 earnings years
18
- .reduce((partialSum, a) => partialSum + a, 0); // and finally sum them
19
- const AIME = top35YearsEarnings / (12 * lookbackYears);
20
- const normalMonthlyBenefit = (() => {
21
+ .slice(0, lookbackYears); // grab the highest 35 earnings years
22
+ console.log(top35YearsEarningsArr);
23
+ const top35YearsEarnings = top35YearsEarningsArr.reduce((partialSum, a) => partialSum + a, 0); // and finally sum them
24
+ // https://www.ssa.gov/oact/cola/Benefits.html
25
+ // "We then round the resulting average amount down to the next lower dollar amount"
26
+ const AIME = Math.floor(top35YearsEarnings / (12 * lookbackYears));
27
+ // https://www.ssa.gov/OP_Home/handbook/handbook.07/handbook-0738.html
28
+ // Calculations that are not a multiple of 10 cents are rounded to the next lower multiple of 10 cents. For example, $100.18 is rounded down to $100.10.
29
+ const PIA = (() => {
21
30
  let monthlyBenefit = 0;
22
31
  if (AIME <= firstBendPoint) {
23
32
  monthlyBenefit = 0.9 * AIME;
@@ -30,20 +39,27 @@ function calc(earnings) {
30
39
  monthlyBenefit = 0.9 * firstBendPoint + 0.32 * (secondBendPoint - firstBendPoint) + 0.15 * (AIME - secondBendPoint);
31
40
  }
32
41
  }
33
- return Math.floor(monthlyBenefit * 10.0) / 10.0;
34
- ;
42
+ return roundToFloorTenCents(monthlyBenefit);
35
43
  })();
36
- const reducedMonthlyBenefit = Math.floor(((0.7 * normalMonthlyBenefit) * 10) / 10);
44
+ const reducedMonthlyBenefit = Math.floor(0.7 * PIA);
45
+ const normalMonthlyBenefit = Math.floor(PIA);
37
46
  const results = {
38
- "Top35YearsEarnings": top35YearsEarnings.toFixed(2),
39
- "AIME": AIME.toFixed(2),
40
- "FirstBendPoint": firstBendPoint.toFixed(2),
41
- "SecondBendPoint": secondBendPoint.toFixed(2),
42
- "NormalMonthlyBenefit": normalMonthlyBenefit.toFixed(2),
43
- "NormalAnnualBenefit": (normalMonthlyBenefit * 12).toFixed(2),
44
- "ReducedMonthlyBenefit": reducedMonthlyBenefit.toFixed(2),
45
- "ReducedAnnualBenefit": (reducedMonthlyBenefit * 12).toFixed(2),
47
+ "Top35YearsEarnings": top35YearsEarnings,
48
+ "AIME": AIME,
49
+ "FirstBendPoint": firstBendPoint,
50
+ "SecondBendPoint": secondBendPoint,
51
+ "NormalMonthlyBenefit": normalMonthlyBenefit,
52
+ "NormalAnnualBenefit": PIA * 12,
53
+ "ReducedMonthlyBenefit": reducedMonthlyBenefit,
54
+ "ReducedAnnualBenefit": reducedMonthlyBenefit * 12,
46
55
  };
47
56
  return results;
57
+ };
58
+ function roundToFloorTenCents(amount) {
59
+ // Convert the amount to fractional dimes
60
+ let dimes = amount * 10;
61
+ // floor to only whole dimes
62
+ dimes = Math.floor(dimes);
63
+ // Convert back to dollars and return
64
+ return (dimes / 10);
48
65
  }
49
- module.exports = calc;
package/lib/model.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import xml2js from 'xml2js';
2
+ import fs from 'fs/promises';
3
+ const NS = 'osss';
4
+ const parser = new xml2js.Parser();
5
+ const supportedVersion = "http://ssa.gov/osss/schemas/2.0";
6
+ async function getWages(fileName) {
7
+ const data = await fs.readFile(fileName);
8
+ const result = await parser.parseStringPromise(data);
9
+ const schema = result[`${NS}:OnlineSocialSecurityStatementData`]['$'][`xmlns:${NS}`];
10
+ if (schema !== supportedVersion) {
11
+ throw `${schema} is not supported (${supportedVersion})`;
12
+ }
13
+ const results = result[`${NS}:OnlineSocialSecurityStatementData`][`${NS}:EarningsRecord`][0][`${NS}:Earnings`];
14
+ const earnings = results.reduce((acc, earn) => (acc[earn['$'].startYear] = parseInt(earn[`${NS}:FicaEarnings`][0]), acc), {});
15
+ return earnings;
16
+ }
17
+ export default getWages;
package/lib/wage-index.js CHANGED
@@ -1,8 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wageIndex = void 0;
4
- const compound = require('compound-calc');
5
- exports.wageIndex = {
1
+ import { compound } from 'compound-calc';
2
+ // https://www.ssa.gov/OACT/COLA/awiseries.html
3
+ export const wageIndex = {
6
4
  1951: 2799.16,
7
5
  1952: 2973.32,
8
6
  1953: 3139.44,
@@ -73,44 +71,46 @@ exports.wageIndex = {
73
71
  2018: 52145.8,
74
72
  2019: 54099.99,
75
73
  2020: 55628.6,
74
+ 2021: 60575.07,
75
+ 2022: 63795.13
76
76
  };
77
77
  const shortRangeIntermediate = {
78
- 2021: 58743.07,
79
- 2022: 62583.15,
80
- 2023: 65571.72,
81
- 2024: 68371.79,
82
- 2025: 71147.65,
83
- 2026: 73980.60,
84
- 2027: 76857.74,
85
- 2028: 79761.29,
86
- 2029: 82702.63,
87
- 2030: 85713.03,
88
- 2031: 88836.46,
78
+ 2023: 66147.17,
79
+ 2024: 68627.58,
80
+ 2025: 71411.99,
81
+ 2026: 74348.48,
82
+ 2027: 77393.67,
83
+ 2028: 80510.73,
84
+ 2029: 83757.03,
85
+ 2030: 87106.49,
86
+ 2031: 90574.48,
87
+ 2032: 93995.33,
89
88
  };
90
89
  const longRangeIntermediate = {
91
- 2035: 102530.83,
92
- 2040: 122417.01,
93
- 2045: 145582.26,
94
- 2050: 172887.64,
95
- 2055: 205634.94,
96
- 2060: 244870.78,
97
- 2065: 291767.28,
98
- 2070: 347552.77,
99
- 2075: 413851.91,
100
- 2080: 492391.60,
101
- 2085: 585488.00,
102
- 2090: 696610.41,
103
- 2095: 829710.10,
104
- 2100: 988243.75,
90
+ 2035: 104726.27,
91
+ 2040: 125312.66,
92
+ 2045: 149423.47,
93
+ 2050: 177750.26,
94
+ 2055: 211432.09,
95
+ 2060: 251610.19,
96
+ 2065: 299758.28,
97
+ 2070: 357187.25,
98
+ 2075: 425523.96,
99
+ 2080: 506962.67,
100
+ 2085: 603863.51,
101
+ 2090: 719124.11,
102
+ 2095: 856091.73,
103
+ 2100: 1019162.94,
105
104
  };
106
105
  const fillBlanks = (vals) => Object.entries(vals).reduce((acc, [year, P], index, arr) => {
107
106
  let thing;
107
+ const iYear = parseInt(year);
108
108
  if (arr[index + 1]) {
109
109
  const [nextYear, A] = arr[index + 1];
110
110
  const t = parseInt(nextYear) - parseInt(year);
111
111
  const r = Math.pow((A / P), (1 / t)) - 1;
112
112
  const vals = compound(P, 0, t, r).result.slice(0, -1);
113
- const ret = vals.reduce((accx, cur, i) => ((accx[(parseInt(year) + i).toString()] = cur) && accx), {});
113
+ const ret = vals.reduce((accx, cur, i) => ((accx[iYear + i] = cur) && accx), {});
114
114
  thing = Object.assign(Object.assign({}, acc), ret);
115
115
  }
116
116
  else {
@@ -118,5 +118,3 @@ const fillBlanks = (vals) => Object.entries(vals).reduce((acc, [year, P], index,
118
118
  }
119
119
  return thing;
120
120
  }, {});
121
- // console.log(fillBlanks(longRangeIntermediate));
122
- console.log(fillBlanks({ 2000: 38915, 2021: 108494 }));
package/package.json CHANGED
@@ -1,29 +1,41 @@
1
1
  {
2
2
  "name": "social-security-calculator",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Calculate estimated Social Security Benefits",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
+ "type": "module",
7
8
  "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1",
9
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
9
10
  "build": "tsc"
10
11
  },
11
12
  "repository": {
12
13
  "type": "git",
13
14
  "url": "git+https://github.com/cmcnulty/SocialSecurityCalculator.git"
14
15
  },
15
- "keywords": [],
16
+ "keywords": [
17
+ "calculator",
18
+ "social",
19
+ "security",
20
+ "ssi"
21
+ ],
16
22
  "author": "",
17
- "license": "ISC",
23
+ "license": "MIT",
18
24
  "bugs": {
19
25
  "url": "https://github.com/cmcnulty/SocialSecurityCalculator/issues"
20
26
  },
21
27
  "homepage": "https://github.com/cmcnulty/SocialSecurityCalculator#readme",
22
28
  "devDependencies": {
29
+ "@jest/globals": "^29.7.0",
30
+ "@types/jest": "^29.5.8",
31
+ "@types/xml2js": "^0.4.11",
32
+ "compound-calc": "^4.0.3",
33
+ "jest": "^29.7.0",
34
+ "ts-jest": "^29.1.1",
23
35
  "typescript": "^4.8.3"
24
36
  },
25
37
  "dependencies": {
26
- "compound-calc": "^2.0.0"
38
+ "xml2js": "^0.6.2"
27
39
  },
28
40
  "files": [
29
41
  "lib"
package/lib/index.d.ts DELETED
@@ -1,15 +0,0 @@
1
- export = calc;
2
- declare function calc(earnings: any): {
3
- Top35YearsEarnings: any;
4
- AIME: string;
5
- FirstBendPoint: string;
6
- SecondBendPoint: string;
7
- NormalMonthlyBenefit: string;
8
- NormalAnnualBenefit: string;
9
- ReducedMonthlyBenefit: string;
10
- ReducedAnnualBenefit: string;
11
- };
12
- declare namespace calc {
13
- export { __esModule };
14
- }
15
- declare const __esModule: boolean;
package/lib/package.bak DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "name": "social-security-calculator",
3
- "version": "0.0.5",
4
- "description": "Calculate estimated Social Security Benefits",
5
- "main": "index.js",
6
- "types": "index.d.ts",
7
- "files": [
8
- "**/*"
9
- ],
10
- "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
12
- "build": "tsc"
13
- },
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/cmcnulty/SocialSecurityCalculator.git"
17
- },
18
- "keywords": [],
19
- "author": "",
20
- "license": "ISC",
21
- "bugs": {
22
- "url": "https://github.com/cmcnulty/SocialSecurityCalculator/issues"
23
- },
24
- "homepage": "https://github.com/cmcnulty/SocialSecurityCalculator#readme",
25
- "devDependencies": {
26
- "typescript": "^4.8.3"
27
- },
28
- "dependencies": {
29
- "compound-calc": "^2.0.0"
30
- }
31
- }