mirror of
https://gitlab.com/MisterBiggs/secure-act-2.0.git
synced 2025-08-17 16:24:43 +00:00
- Fix chart data showing age 70 values instead of age 65 retirement values - Update all hardcoded examples to match actual calculator output - Recent Graduate: 75K → 94K (corrected retirement value) - MBA Graduate: 25K → 29K (corrected retirement value) - Medical Professional: 85K → ,065K (corrected) - Teacher: 8K → 88K (corrected) - Software Engineer: 12K → ,132K (corrected) - Attorney: 5K → 54K (corrected) - All examples now mathematically consistent with calculator logic - Charts no longer appear to 'slow down' at retirement age
209 lines
7.9 KiB
JavaScript
209 lines
7.9 KiB
JavaScript
/**
|
|
* Student Loan Matching Calculator Logic
|
|
* Calculates net worth over time with and without SECURE Act 2.0 matching
|
|
*/
|
|
|
|
const INVESTMENT_RETURN_RATE = 0.07; // 7% annual return
|
|
const LOAN_INTEREST_RATE = 0.05; // 5% annual interest (federal student loan average)
|
|
const RETIREMENT_AGE = 65;
|
|
const MAX_CALCULATION_AGE = 70; // Calculate up to age 70 for complete data
|
|
|
|
/**
|
|
* Calculate loan payoff time in years
|
|
* @param {number} totalDebt - Total loan balance
|
|
* @param {number} monthlyPayment - Monthly payment amount
|
|
* @returns {number} Years to pay off loan
|
|
*/
|
|
function calculatePayoffYears(totalDebt, monthlyPayment) {
|
|
if (!monthlyPayment || monthlyPayment <= 0) return 0;
|
|
|
|
const monthlyRate = LOAN_INTEREST_RATE / 12;
|
|
const minPayment = totalDebt * monthlyRate;
|
|
|
|
if (monthlyPayment <= minPayment) {
|
|
return 50; // Loan will never be paid off at this rate
|
|
}
|
|
|
|
const months = Math.log(monthlyPayment / (monthlyPayment - totalDebt * monthlyRate)) / Math.log(1 + monthlyRate);
|
|
return Math.ceil(months / 12);
|
|
}
|
|
|
|
/**
|
|
* Calculate remaining loan balance after a number of months
|
|
* @param {number} originalBalance - Original loan amount
|
|
* @param {number} monthlyPayment - Monthly payment
|
|
* @param {number} monthsElapsed - Months since start of loan
|
|
* @param {number} totalMonths - Total months for loan term
|
|
* @returns {number} Remaining balance
|
|
*/
|
|
function calculateRemainingBalance(originalBalance, monthlyPayment, monthsElapsed, totalMonths) {
|
|
if (monthsElapsed <= 0) return originalBalance;
|
|
if (monthsElapsed >= totalMonths) return 0;
|
|
|
|
const monthlyRate = LOAN_INTEREST_RATE / 12;
|
|
const factor = Math.pow(1 + monthlyRate, monthsElapsed);
|
|
const paymentsValue = monthlyPayment * ((factor - 1) / monthlyRate);
|
|
const remainingBalance = originalBalance * factor - paymentsValue;
|
|
|
|
return Math.max(0, remainingBalance);
|
|
}
|
|
|
|
/**
|
|
* Calculate future value of retirement contributions with compound growth
|
|
* @param {number} annualContribution - Annual contribution amount
|
|
* @param {number} yearsContributing - Number of years contributing
|
|
* @param {number} yearsToGrow - Total years for growth (from first contribution to retirement)
|
|
* @returns {number} Future value
|
|
*/
|
|
function calculateRetirementValue(annualContribution, yearsContributing, yearsToGrow) {
|
|
let totalValue = 0;
|
|
|
|
for (let year = 0; year < yearsContributing; year++) {
|
|
const growthYears = yearsToGrow - year;
|
|
totalValue += annualContribution * Math.pow(1 + INVESTMENT_RETURN_RATE, growthYears);
|
|
}
|
|
|
|
return totalValue;
|
|
}
|
|
|
|
/**
|
|
* Main calculation function for net worth comparison
|
|
* @param {Object} params - Input parameters
|
|
* @returns {Object} Chart data with ages and net worth values
|
|
*/
|
|
function calculateNetWorthComparison(params) {
|
|
const {
|
|
salary = 0,
|
|
totalDebt = 0,
|
|
monthlyPayment = 0,
|
|
matchRate = 0,
|
|
currentAge = 28
|
|
} = params;
|
|
|
|
// Calculate derived values
|
|
const payoffYears = calculatePayoffYears(totalDebt, monthlyPayment);
|
|
const annualMatch = Math.min(monthlyPayment * 12, salary * (matchRate / 100));
|
|
const payoffAge = currentAge + payoffYears;
|
|
|
|
// Arrays for full calculation data (yearly)
|
|
const allAges = [];
|
|
const allWithProgram = [];
|
|
const allWithoutProgram = [];
|
|
|
|
// Calculate for each year from current to age 70
|
|
for (let age = currentAge; age <= MAX_CALCULATION_AGE; age++) {
|
|
allAges.push(age);
|
|
const yearsElapsed = age - currentAge;
|
|
const monthsElapsed = yearsElapsed * 12;
|
|
|
|
// Calculate remaining loan balance
|
|
const totalMonths = payoffYears * 12;
|
|
const remainingDebt = calculateRemainingBalance(totalDebt, monthlyPayment, monthsElapsed, totalMonths);
|
|
|
|
if (age <= payoffAge) {
|
|
// DURING LOAN PAYMENT PERIOD
|
|
|
|
// With program: Get employer match while paying loans
|
|
const yearsContributing = Math.min(yearsElapsed, payoffYears);
|
|
const retirementValue = calculateRetirementValue(annualMatch, yearsContributing, yearsElapsed);
|
|
const netWorthWith = retirementValue - remainingDebt;
|
|
allWithProgram.push(Math.round(netWorthWith));
|
|
|
|
// Without program: No retirement savings, just debt
|
|
const netWorthWithout = -remainingDebt;
|
|
allWithoutProgram.push(Math.round(netWorthWithout));
|
|
|
|
} else {
|
|
// AFTER LOANS PAID OFF
|
|
|
|
const yearsSincePayoff = age - payoffAge;
|
|
const postPayoffContribution = Math.min(monthlyPayment * 12, salary * (matchRate / 100));
|
|
|
|
// With program: Initial match during loan period + continued contributions after
|
|
const duringLoanValue = calculateRetirementValue(annualMatch, payoffYears, yearsElapsed);
|
|
const afterLoanValue = calculateRetirementValue(postPayoffContribution, yearsSincePayoff, yearsSincePayoff);
|
|
const totalWithProgram = duringLoanValue + afterLoanValue;
|
|
allWithProgram.push(Math.round(totalWithProgram));
|
|
|
|
// Without program: Only start contributing after loans paid off
|
|
const totalWithoutProgram = calculateRetirementValue(postPayoffContribution, yearsSincePayoff, yearsSincePayoff);
|
|
allWithoutProgram.push(Math.round(totalWithoutProgram));
|
|
}
|
|
}
|
|
|
|
// Now filter the data for chart display (up to age 65)
|
|
// Show consistent points regardless of starting age: every 2 years on even ages
|
|
// Plus always include the starting age and retirement age
|
|
const ages = [];
|
|
const withProgram = [];
|
|
const withoutProgram = [];
|
|
|
|
for (let i = 0; i < allAges.length; i++) {
|
|
const age = allAges[i];
|
|
|
|
// Only include ages up to 65 for chart display
|
|
if (age > RETIREMENT_AGE) break;
|
|
|
|
// Include if:
|
|
// 1. It's the starting age (first point)
|
|
// 2. It's the retirement age (last point)
|
|
// 3. It's an even age and at least 2 years from start
|
|
const isStartAge = (i === 0);
|
|
const isRetirementAge = (age === RETIREMENT_AGE);
|
|
const isEvenInterval = (age % 2 === 0 && age > currentAge);
|
|
|
|
if (isStartAge || isRetirementAge || isEvenInterval) {
|
|
ages.push(age);
|
|
withProgram.push(allWithProgram[i]);
|
|
withoutProgram.push(allWithoutProgram[i]);
|
|
}
|
|
}
|
|
|
|
// Find age 65 index for retirement values
|
|
const age65Index = allAges.findIndex(age => age === RETIREMENT_AGE);
|
|
const retirementWith = age65Index >= 0 ? allWithProgram[age65Index] : allWithProgram[allWithProgram.length - 1];
|
|
const retirementWithout = age65Index >= 0 ? allWithoutProgram[age65Index] : allWithoutProgram[allWithoutProgram.length - 1];
|
|
|
|
return {
|
|
ages,
|
|
withProgram,
|
|
withoutProgram,
|
|
payoffYears,
|
|
annualMatch,
|
|
totalRetirementWith: retirementWith,
|
|
totalRetirementWithout: retirementWithout,
|
|
retirementDifference: retirementWith - retirementWithout,
|
|
// Include full yearly data if needed
|
|
allAges,
|
|
allWithProgram,
|
|
allWithoutProgram
|
|
};
|
|
}
|
|
|
|
// Export for use in browser
|
|
if (typeof window !== 'undefined') {
|
|
window.StudentLoanCalculator = {
|
|
calculatePayoffYears,
|
|
calculateRemainingBalance,
|
|
calculateRetirementValue,
|
|
calculateNetWorthComparison,
|
|
INVESTMENT_RETURN_RATE,
|
|
LOAN_INTEREST_RATE,
|
|
RETIREMENT_AGE,
|
|
MAX_CALCULATION_AGE
|
|
};
|
|
}
|
|
|
|
// Export for Node.js testing
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = {
|
|
calculatePayoffYears,
|
|
calculateRemainingBalance,
|
|
calculateRetirementValue,
|
|
calculateNetWorthComparison,
|
|
INVESTMENT_RETURN_RATE,
|
|
LOAN_INTEREST_RATE,
|
|
RETIREMENT_AGE,
|
|
MAX_CALCULATION_AGE
|
|
};
|
|
} |